Compare commits

...

14 Commits

Author SHA1 Message Date
Conrad Irwin
b4be47ba1e zed 0.124.4 2024-02-23 11:06:10 -07:00
Conrad Irwin
dae95e03e6 Disable swift for now (#8291)
It causes segfaults on load

Release Notes:

- Fixed a segfault opening a Swift file with the Swift extension
installed.
2024-02-23 11:05:15 -07:00
Conrad Irwin
4faf3acf67 zed 0.124.3 2024-02-23 10:41:41 -07:00
Conrad Irwin
9e9ea6fcb4 Allow typing space in workspace::SendKeystrokes (#8288)
Fixes #8222

Release Notes:

- N/A
2024-02-23 10:40:56 -07:00
Uladzislau Kaminski
e3d5a0f649 Fix for toggles on the Welcome page (#8159)
Release Notes:

The issue is that when welcome page appears settings.json file is not
created yet. So the idea of this fix is to create the file in case it is
not there yet.

- Fixed the toggles on the welcome screen not working if no settings
file exists yet.
([#8153](https://github.com/zed-industries/zed/issues/8153)).

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
Co-authored-by: Marshall <marshall@zed.dev>
2024-02-23 18:24:46 +01:00
Conrad Irwin
bd317eea49 zed 0.124.2 2024-02-23 08:34:08 -07:00
Conrad Irwin
77cdc280c2 Fix a panic in the assistant panel (#8244)
Release Notes:

- Fixed a panic in the assistant panel when the app is shutting down.
2024-02-23 08:33:04 -07:00
Conrad Irwin
1ba33763a4 fix vim panics (#8245)
Release Notes:

- vim: Fixed a panic when using H/M/L when scrolled beyond the end of
the buffer
2024-02-23 08:33:04 -07:00
Thorsten Ball
292d32eb70 Pick up more home dir shell env when spawning (#8273)
Release Notes:

- Improved how Zed picks up shell environment when spawned.
2024-02-23 15:20:54 +01:00
Thorsten Ball
10df9dfca1 Detect and possibly use user-installed gopls / zls language servers (#8188)
After a lot of back-and-forth, this is a small attempt to implement
solutions (1) and (3) in
https://github.com/zed-industries/zed/issues/7902. The goal is to have a
minimal change that helps users get started with Zed, until we have
extensions ready.

Release Notes:

- Added detection of user-installed `gopls` to Go language server
adapter. If a user has `gopls` in `$PATH` when opening a worktree, it
will be used.
- Added detection of user-installed `zls` to Zig language server
adapter. If a user has `zls` in `$PATH` when opening a worktree, it will
be used.

Example:

I don't have `go` installed globally, but I do have `gopls`:

```
~ $ which go
go not found
~ $ which gopls
/Users/thorstenball/code/go/bin/gopls
```

But I do have `go` in a project's directory:

```
~/tmp/go-testing φ which go
/Users/thorstenball/.local/share/mise/installs/go/1.21.5/go/bin/go
~/tmp/go-testing φ which gopls
/Users/thorstenball/code/go/bin/gopls
```

With current Zed when I run `zed ~/tmp/go-testing`, I'd get the dreaded
error:

![screenshot-2024-02-23-11 14
08@2x](https://github.com/zed-industries/zed/assets/1185253/822ea59b-c63e-4102-a50e-75501cc4e0e3)

But with the changes in this PR, it works:

```
[2024-02-23T11:14:42+01:00 INFO  language::language_registry] starting language server "gopls", path: "/Users/thorstenball/tmp/go-testing", id: 1
[2024-02-23T11:14:42+01:00 INFO  language::language_registry] found user-installed language server for Go. path: "/Users/thorstenball/code/go/bin/gopls", arguments: ["-mode=stdio"]
[2024-02-23T11:14:42+01:00 INFO  lsp] starting language server. binary path: "/Users/thorstenball/code/go/bin/gopls", working directory: "/Users/thorstenball/tmp/go-testing", args: ["-mode=stdio"]
```

---------

Co-authored-by: Antonio <antonio@zed.dev>
2024-02-23 14:01:22 +01:00
Kirill Bulatov
57426b925e Ensure default prettier installs correctly when certain FS entries are missing (#8261)
Fixes https://github.com/zed-industries/zed/issues/7865

* bind default prettier (re)installation decision to
`prettier_server.js` existence
* ensure the `prettier_server.js` file is created last, after all
default prettier packages installed
* ensure that default prettier directory exists before installing the
packages
* reinstall default prettier if the `prettier_server.js` file is
different from what Zed expects

Release Notes:

- Fixed incorrect default prettier installation process
2024-02-23 12:30:08 +02:00
Kirill Bulatov
e12d617264 zed 0.124.1 2024-02-22 17:10:37 +02:00
Kirill Bulatov
ca1a95e6e2 Require prerelease eslint version (#8197)
Fixes https://github.com/zed-industries/zed/issues/7650

Release Notes:

- Fixed eslint diagnostics not showing up due to old eslint version used
2024-02-22 16:38:31 +02:00
Joseph T. Lyons
bc8e7e0cc7 v0.124.x preview 2024-02-21 12:22:24 -05:00
53 changed files with 521 additions and 97 deletions

26
Cargo.lock generated
View File

@@ -1349,7 +1349,7 @@ dependencies = [
"rustc-hash",
"shlex",
"syn 2.0.48",
"which",
"which 4.4.2",
]
[[package]]
@@ -4340,11 +4340,11 @@ dependencies = [
[[package]]
name = "home"
version = "0.5.5"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -6915,6 +6915,7 @@ dependencies = [
"toml 0.8.10",
"unindent",
"util",
"which 6.0.0",
]
[[package]]
@@ -7024,7 +7025,7 @@ dependencies = [
"prost-types 0.9.0",
"regex",
"tempfile",
"which",
"which 4.4.2",
]
[[package]]
@@ -11396,6 +11397,19 @@ dependencies = [
"rustix 0.38.30",
]
[[package]]
name = "which"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c"
dependencies = [
"either",
"home",
"once_cell",
"rustix 0.38.30",
"windows-sys 0.52.0",
]
[[package]]
name = "whoami"
version = "1.4.1"
@@ -11911,7 +11925,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.124.0"
version = "0.124.4"
dependencies = [
"activity_indicator",
"ai",

View File

@@ -278,6 +278,7 @@ unindent = "0.1.7"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4"] }
wasmtime = "16"
which = "6.0.0"
sys-locale = "0.3.1"
[patch.crates-io]

View File

@@ -122,16 +122,13 @@ impl AssistantPanel {
.await
.log_err()
.unwrap_or_default();
let (api_url, model_name) = cx
.update(|cx| {
let settings = AssistantSettings::get_global(cx);
(
settings.openai_api_url.clone(),
settings.default_open_ai_model.full_name().to_string(),
)
})
.log_err()
.unwrap();
let (api_url, model_name) = cx.update(|cx| {
let settings = AssistantSettings::get_global(cx);
(
settings.openai_api_url.clone(),
settings.default_open_ai_model.full_name().to_string(),
)
})?;
let completion_provider = OpenAiCompletionProvider::new(
api_url,
model_name,

View File

@@ -428,6 +428,8 @@ impl Copilot {
let binary = LanguageServerBinary {
path: node_path,
arguments,
// TODO: We could set HTTP_PROXY etc here and fix the copilot issue.
env: None,
};
let server = LanguageServer::new(

View File

@@ -406,6 +406,10 @@ impl ExtensionStore {
}));
for language_name in &languages_to_add {
if language_name.as_ref() == "Swift" {
continue;
}
let language = manifest.languages.get(language_name.as_ref()).unwrap();
let mut language_path = self.extensions_dir.clone();
language_path.extend([language.extension.as_ref(), language.path.as_path()]);

View File

@@ -108,6 +108,35 @@ impl Keystroke {
ime_key,
})
}
/// Returns a new keystroke with the ime_key filled.
/// This is used for dispatch_keystroke where we want users to
/// be able to simulate typing "space", etc.
pub fn with_simulated_ime(mut self) -> Self {
if self.ime_key.is_none()
&& !self.modifiers.command
&& !self.modifiers.control
&& !self.modifiers.function
&& !self.modifiers.alt
{
self.ime_key = match self.key.as_str() {
"space" => Some(" ".into()),
"tab" => Some("\t".into()),
"enter" => Some("\n".into()),
"up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end"
| "delete" | "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6"
| "f7" | "f8" | "f9" | "f10" | "f11" | "f12" => None,
key => {
if self.modifiers.shift {
Some(key.to_uppercase())
} else {
Some(key.into())
}
}
}
}
self
}
}
impl std::fmt::Display for Keystroke {

View File

@@ -1101,18 +1101,8 @@ impl<'a> WindowContext<'a> {
/// Dispatch a given keystroke as though the user had typed it.
/// You can create a keystroke with Keystroke::parse("").
pub fn dispatch_keystroke(&mut self, mut keystroke: Keystroke) -> bool {
if keystroke.ime_key.is_none()
&& !keystroke.modifiers.command
&& !keystroke.modifiers.control
&& !keystroke.modifiers.function
{
keystroke.ime_key = Some(if keystroke.modifiers.shift {
keystroke.key.to_uppercase().clone()
} else {
keystroke.key.clone()
})
}
pub fn dispatch_keystroke(&mut self, keystroke: Keystroke) -> bool {
let keystroke = keystroke.with_simulated_ime();
if self.dispatch_event(PlatformInput::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held: false,

View File

@@ -38,6 +38,7 @@ use serde_json::Value;
use std::{
any::Any,
cell::RefCell,
ffi::OsString,
fmt::Debug,
hash::Hash,
mem,
@@ -140,6 +141,14 @@ impl CachedLspAdapter {
})
}
pub fn check_if_user_installed(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<Task<Option<LanguageServerBinary>>> {
self.adapter.check_if_user_installed(delegate, cx)
}
pub async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
@@ -240,6 +249,11 @@ impl CachedLspAdapter {
pub trait LspAdapterDelegate: Send + Sync {
fn show_notification(&self, message: &str, cx: &mut AppContext);
fn http_client(&self) -> Arc<dyn HttpClient>;
fn which_command(
&self,
command: OsString,
cx: &AppContext,
) -> Task<Option<(PathBuf, HashMap<String, String>)>>;
}
#[async_trait]
@@ -248,6 +262,14 @@ pub trait LspAdapter: 'static + Send + Sync {
fn short_name(&self) -> &'static str;
fn check_if_user_installed(
&self,
_: &Arc<dyn LspAdapterDelegate>,
_: &mut AsyncAppContext,
) -> Option<Task<Option<LanguageServerBinary>>> {
None
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,

View File

@@ -558,34 +558,41 @@ impl LanguageRegistry {
let task = {
let container_dir = container_dir.clone();
cx.spawn(move |mut cx| async move {
login_shell_env_loaded.await;
// First we check whether the adapter can give us a user-installed binary.
// If so, we do *not* want to cache that, because each worktree might give us a different
// binary:
//
// worktree 1: user-installed at `.bin/gopls`
// worktree 2: user-installed at `~/bin/gopls`
// worktree 3: no gopls found in PATH -> fallback to Zed installation
//
// We only want to cache when we fall back to the global one,
// because we don't want to download and overwrite our global one
// for each worktree we might have open.
let entry = this
.lsp_binary_paths
.lock()
.entry(adapter.name.clone())
.or_insert_with(|| {
let adapter = adapter.clone();
let language = language.clone();
let delegate = delegate.clone();
cx.spawn(|cx| {
get_binary(
adapter,
language,
delegate,
container_dir,
lsp_binary_statuses,
cx,
)
.map_err(Arc::new)
})
.shared()
})
.clone();
let user_binary_task = check_user_installed_binary(
adapter.clone(),
language.clone(),
delegate.clone(),
&mut cx,
);
let binary = if let Some(user_binary) = user_binary_task.await {
user_binary
} else {
// If we want to install a binary globally, we need to wait for
// the login shell to be set on our process.
login_shell_env_loaded.await;
let binary = match entry.await {
Ok(binary) => binary,
Err(err) => anyhow::bail!("{err}"),
get_or_install_binary(
this,
&adapter,
language,
&delegate,
&cx,
container_dir,
lsp_binary_statuses,
)
.await?
};
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
@@ -724,6 +731,62 @@ impl LspBinaryStatusSender {
}
}
async fn check_user_installed_binary(
adapter: Arc<CachedLspAdapter>,
language: Arc<Language>,
delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<LanguageServerBinary> {
let Some(task) = adapter.check_if_user_installed(&delegate, cx) else {
return None;
};
task.await.and_then(|binary| {
log::info!(
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
language.name(),
binary.path,
binary.arguments
);
Some(binary)
})
}
async fn get_or_install_binary(
registry: Arc<LanguageRegistry>,
adapter: &Arc<CachedLspAdapter>,
language: Arc<Language>,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &AsyncAppContext,
container_dir: Arc<Path>,
lsp_binary_statuses: LspBinaryStatusSender,
) -> Result<LanguageServerBinary> {
let entry = registry
.lsp_binary_paths
.lock()
.entry(adapter.name.clone())
.or_insert_with(|| {
let adapter = adapter.clone();
let language = language.clone();
let delegate = delegate.clone();
cx.spawn(|cx| {
get_binary(
adapter,
language,
delegate,
container_dir,
lsp_binary_statuses,
cx,
)
.map_err(Arc::new)
})
.shared()
})
.clone();
entry.await.map_err(|err| anyhow!("{:?}", err))
}
async fn get_binary(
adapter: Arc<CachedLspAdapter>,
language: Arc<Language>,
@@ -757,15 +820,20 @@ async fn get_binary(
.await
{
statuses.send(language.clone(), LanguageServerBinaryStatus::Cached);
return Ok(binary);
} else {
statuses.send(
language.clone(),
LanguageServerBinaryStatus::Failed {
error: format!("{:?}", error),
},
log::info!(
"failed to fetch newest version of language server {:?}. falling back to using {:?}",
adapter.name,
binary.path.display()
);
return Ok(binary);
}
statuses.send(
language.clone(),
LanguageServerBinaryStatus::Failed {
error: format!("{:?}", error),
},
);
}
binary
@@ -779,14 +847,23 @@ async fn fetch_latest_binary(
lsp_binary_statuses_tx: LspBinaryStatusSender,
) -> Result<LanguageServerBinary> {
let container_dir: Arc<Path> = container_dir.into();
lsp_binary_statuses_tx.send(
language.clone(),
LanguageServerBinaryStatus::CheckingForUpdate,
);
log::info!(
"querying GitHub for latest version of language server {:?}",
adapter.name.0
);
let version_info = adapter.fetch_latest_server_version(delegate).await?;
lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloading);
log::info!(
"checking if Zed already installed or fetching version for language server {:?}",
adapter.name.0
);
let binary = adapter
.fetch_server_binary(version_info, container_dir.to_path_buf(), delegate)
.await?;

View File

@@ -55,6 +55,7 @@ pub enum IoKind {
pub struct LanguageServerBinary {
pub path: PathBuf,
pub arguments: Vec<OsString>,
pub env: Option<HashMap<String, String>>,
}
/// A running language server process.
@@ -189,6 +190,7 @@ impl LanguageServer {
let mut server = process::Command::new(&binary.path)
.current_dir(working_dir)
.args(binary.arguments)
.envs(binary.env.unwrap_or_default())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())

View File

@@ -192,6 +192,7 @@ impl Prettier {
LanguageServerBinary {
path: node_path,
arguments: vec![prettier_server.into(), prettier_dir.as_path().into()],
env: None,
},
Path::new("/"),
None,

View File

@@ -65,6 +65,7 @@ text.workspace = true
thiserror.workspace = true
toml.workspace = true
util.workspace = true
which.workspace = true
[dev-dependencies]
client = { workspace = true, features = ["test-support"] }

View File

@@ -70,9 +70,14 @@ pub(super) async fn format_with_prettier(
match prettier.format(buffer, buffer_path, cx).await {
Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)),
Err(e) => {
log::error!(
"Prettier instance from {prettier_path:?} failed to format a buffer: {e:#}"
);
match prettier_path {
Some(prettier_path) => log::error!(
"Prettier instance from path {prettier_path:?} failed to format a buffer: {e:#}"
),
None => log::error!(
"Default prettier instance failed to format a buffer: {e:#}"
),
}
}
}
}
@@ -366,6 +371,7 @@ fn register_new_prettier(
}
async fn install_prettier_packages(
fs: &dyn Fs,
plugins_to_install: HashSet<&'static str>,
node: Arc<dyn NodeRuntime>,
) -> anyhow::Result<()> {
@@ -385,18 +391,32 @@ async fn install_prettier_packages(
.await
.context("fetching latest npm versions")?;
log::info!("Fetching default prettier and plugins: {packages_to_versions:?}");
let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path();
match fs.metadata(default_prettier_dir).await.with_context(|| {
format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
})? {
Some(prettier_dir_metadata) => anyhow::ensure!(
prettier_dir_metadata.is_dir,
"default prettier dir {default_prettier_dir:?} is not a directory"
),
None => fs
.create_dir(default_prettier_dir)
.await
.with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
}
log::info!("Installing default prettier and plugins: {packages_to_versions:?}");
let borrowed_packages = packages_to_versions
.iter()
.map(|(package, version)| (package.as_str(), version.as_str()))
.collect::<Vec<_>>();
node.npm_install_packages(DEFAULT_PRETTIER_DIR.as_path(), &borrowed_packages)
node.npm_install_packages(default_prettier_dir, &borrowed_packages)
.await
.context("fetching formatter packages")?;
anyhow::Ok(())
}
async fn save_prettier_server_file(fs: &dyn Fs) -> Result<(), anyhow::Error> {
async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE);
fs.save(
&prettier_wrapper_path,
@@ -413,6 +433,17 @@ async fn save_prettier_server_file(fs: &dyn Fs) -> Result<(), anyhow::Error> {
Ok(())
}
async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE);
if !fs.is_file(&prettier_wrapper_path).await {
return true;
}
let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
return true;
};
prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
}
impl Project {
pub fn update_prettier_settings(
&self,
@@ -623,6 +654,7 @@ impl Project {
_cx: &mut ModelContext<Self>,
) {
// suppress unused code warnings
let _ = should_write_prettier_server_file;
let _ = install_prettier_packages;
let _ = save_prettier_server_file;
@@ -643,7 +675,6 @@ impl Project {
let Some(node) = self.node.as_ref().cloned() else {
return;
};
log::info!("Initializing default prettier with plugins {new_plugins:?}");
let fs = Arc::clone(&self.fs);
let locate_prettier_installation = match worktree.and_then(|worktree_id| {
self.worktree_for_id(worktree_id, cx)
@@ -689,6 +720,7 @@ impl Project {
}
};
log::info!("Initializing default prettier with plugins {new_plugins:?}");
let plugins_to_install = new_plugins.clone();
let fs = Arc::clone(&self.fs);
let new_installation_task = cx
@@ -703,7 +735,7 @@ impl Project {
if prettier_path.is_some() {
new_plugins.clear();
}
let mut needs_install = false;
let mut needs_install = should_write_prettier_server_file(fs.as_ref()).await;
if let Some(previous_installation_task) = previous_installation_task {
if let Err(e) = previous_installation_task.await {
log::error!("Failed to install default prettier: {e:#}");
@@ -744,8 +776,10 @@ impl Project {
let installed_plugins = new_plugins.clone();
cx.background_executor()
.spawn(async move {
install_prettier_packages(fs.as_ref(), new_plugins, node).await?;
// Save the server file last, so the reinstall need could be determined by the absence of the file.
save_prettier_server_file(fs.as_ref()).await?;
install_prettier_packages(new_plugins, node).await
anyhow::Ok(())
})
.await
.context("prettier & plugins install")

View File

@@ -71,6 +71,8 @@ use smol::lock::Semaphore;
use std::{
cmp::{self, Ordering},
convert::TryInto,
env,
ffi::OsString,
hash::Hash,
mem,
num::NonZeroU32,
@@ -504,11 +506,6 @@ pub enum FormatTrigger {
Manual,
}
struct ProjectLspAdapterDelegate {
project: Model<Project>,
http_client: Arc<dyn HttpClient>,
}
// Currently, formatting operations are represented differently depending on
// whether they come from a language server or an external command.
enum FormatOperation {
@@ -2800,7 +2797,7 @@ impl Project {
fn start_language_server(
&mut self,
worktree: &Model<Worktree>,
worktree_handle: &Model<Worktree>,
adapter: Arc<CachedLspAdapter>,
language: Arc<Language>,
cx: &mut ModelContext<Self>,
@@ -2809,7 +2806,7 @@ impl Project {
return;
}
let worktree = worktree.read(cx);
let worktree = worktree_handle.read(cx);
let worktree_id = worktree.id();
let worktree_path = worktree.abs_path();
let key = (worktree_id, adapter.name.clone());
@@ -2823,7 +2820,7 @@ impl Project {
language.clone(),
adapter.clone(),
Arc::clone(&worktree_path),
ProjectLspAdapterDelegate::new(self, cx),
ProjectLspAdapterDelegate::new(self, worktree_handle, cx),
cx,
) {
Some(pending_server) => pending_server,
@@ -9271,10 +9268,17 @@ impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
}
}
struct ProjectLspAdapterDelegate {
project: Model<Project>,
worktree: Model<Worktree>,
http_client: Arc<dyn HttpClient>,
}
impl ProjectLspAdapterDelegate {
fn new(project: &Project, cx: &ModelContext<Project>) -> Arc<Self> {
fn new(project: &Project, worktree: &Model<Worktree>, cx: &ModelContext<Project>) -> Arc<Self> {
Arc::new(Self {
project: cx.handle(),
worktree: worktree.clone(),
http_client: project.client.http_client(),
})
}
@@ -9289,6 +9293,43 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
fn http_client(&self) -> Arc<dyn HttpClient> {
self.http_client.clone()
}
fn which_command(
&self,
command: OsString,
cx: &AppContext,
) -> Task<Option<(PathBuf, HashMap<String, String>)>> {
let worktree_abs_path = self.worktree.read(cx).abs_path();
let command = command.to_owned();
cx.background_executor().spawn(async move {
let shell_env = load_shell_environment(&worktree_abs_path)
.await
.with_context(|| {
format!(
"failed to determine load login shell environment in {worktree_abs_path:?}"
)
})
.log_err();
if let Some(shell_env) = shell_env {
let shell_path = shell_env.get("PATH");
match which::which_in(&command, shell_path, &worktree_abs_path) {
Ok(command_path) => Some((command_path, shell_env)),
Err(error) => {
log::warn!(
"failed to determine path for command {:?} in shell PATH {:?}: {error}",
command.to_string_lossy(),
shell_path.map(String::as_str).unwrap_or("")
);
None
}
}
} else {
None
}
})
}
}
fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
@@ -9396,3 +9437,55 @@ fn include_text(server: &lsp::LanguageServer) -> bool {
})
.unwrap_or(false)
}
async fn load_shell_environment(dir: &Path) -> Result<HashMap<String, String>> {
let marker = "ZED_SHELL_START";
let shell = env::var("SHELL").context(
"SHELL environment variable is not assigned so we can't source login environment variables",
)?;
let output = smol::process::Command::new(&shell)
.args([
"-i",
"-c",
// What we're doing here is to spawn a shell and then `cd` into
// the project directory to get the env in there as if the user
// `cd`'d into it. We do that because tools like direnv, asdf, ...
// hook into `cd` and only set up the env after that.
//
// The `exit 0` is the result of hours of debugging, trying to find out
// why running this command here, without `exit 0`, would mess
// up signal process for our process so that `ctrl-c` doesn't work
// anymore.
// We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would
// do that, but it does, and `exit 0` helps.
&format!("cd {dir:?}; echo {marker}; /usr/bin/env -0; exit 0;"),
])
.output()
.await
.context("failed to spawn login shell to source login environment variables")?;
anyhow::ensure!(
output.status.success(),
"login shell exited with error {:?}",
output.status
);
let stdout = String::from_utf8_lossy(&output.stdout);
let env_output_start = stdout.find(marker).ok_or_else(|| {
anyhow!(
"failed to parse output of `env` command in login shell: {}",
stdout
)
})?;
let mut parsed_env = HashMap::default();
let env_output = &stdout[env_output_start + marker.len()..];
for line in env_output.split_terminator('\0') {
if let Some(separator_index) = line.find('=') {
let key = line[..separator_index].to_string();
let value = line[separator_index + 1..].to_string();
parsed_env.insert(key, value);
}
}
Ok(parsed_env)
}

View File

@@ -116,13 +116,20 @@ pub fn update_settings_file<T: Settings>(
store.new_text_for_update::<T>(old_text, update)
})?;
let initial_path = paths::SETTINGS.as_path();
let resolved_path = fs
.canonicalize(initial_path)
.await
.with_context(|| format!("Failed to canonicalize settings path {:?}", initial_path))?;
fs.atomic_write(resolved_path.clone(), new_text)
.await
.with_context(|| format!("Failed to write settings to file {:?}", resolved_path))?;
if !fs.is_file(initial_path).await {
fs.atomic_write(initial_path.to_path_buf(), new_text)
.await
.with_context(|| format!("Failed to write settings to file {:?}", initial_path))?;
} else {
let resolved_path = fs.canonicalize(initial_path).await.with_context(|| {
format!("Failed to canonicalize settings path {:?}", initial_path)
})?;
fs.atomic_write(resolved_path.clone(), new_text)
.await
.with_context(|| format!("Failed to write settings to file {:?}", resolved_path))?;
}
anyhow::Ok(())
})
.detach_and_log_err(cx);

View File

@@ -828,7 +828,7 @@ fn next_word_end(
let mut new_point = point;
if new_point.column() < map.line_len(new_point.row()) {
*new_point.column_mut() += 1;
} else if new_point.row() < map.max_buffer_row() {
} else if new_point < map.max_point() {
*new_point.row_mut() += 1;
*new_point.column_mut() = 0;
}
@@ -1110,13 +1110,15 @@ fn window_top(
if let Some(visible_rows) = text_layout_details.visible_rows {
let bottom_row = first_visible_line.row() + visible_rows as u32;
let new_row = (first_visible_line.row() + (times as u32)).min(bottom_row);
let new_row = (first_visible_line.row() + (times as u32))
.min(bottom_row)
.min(map.max_point().row());
let new_col = point.column().min(map.line_len(first_visible_line.row()));
let new_point = DisplayPoint::new(new_row, new_col);
(map.clip_point(new_point, Bias::Left), SelectionGoal::None)
} else {
let new_row = first_visible_line.row() + (times as u32);
let new_row = (first_visible_line.row() + (times as u32)).min(map.max_point().row());
let new_col = point.column().min(map.line_len(first_visible_line.row()));
let new_point = DisplayPoint::new(new_row, new_col);
@@ -1134,8 +1136,12 @@ fn window_middle(
.scroll_anchor
.anchor
.to_display_point(map);
let max_rows = (visible_rows as u32).min(map.max_buffer_row());
let new_row = first_visible_line.row() + (max_rows.div_euclid(2));
let max_visible_rows =
(visible_rows as u32).min(map.max_point().row() - first_visible_line.row());
let new_row =
(first_visible_line.row() + (max_visible_rows / 2) as u32).min(map.max_point().row());
let new_col = point.column().min(map.line_len(new_row));
let new_point = DisplayPoint::new(new_row, new_col);
(map.clip_point(new_point, Bias::Left), SelectionGoal::None)
@@ -1157,12 +1163,12 @@ fn window_bottom(
.to_display_point(map);
let bottom_row = first_visible_line.row()
+ (visible_rows + text_layout_details.scroll_anchor.offset.y - 1.).floor() as u32;
if bottom_row < map.max_buffer_row()
if bottom_row < map.max_point().row()
&& text_layout_details.vertical_scroll_margin as usize > times
{
times = text_layout_details.vertical_scroll_margin.ceil() as usize;
}
let bottom_row_capped = bottom_row.min(map.max_buffer_row());
let bottom_row_capped = bottom_row.min(map.max_point().row());
let new_row = if bottom_row_capped.saturating_sub(times as u32) < first_visible_line.row() {
first_visible_line.row()
} else {

View File

@@ -950,4 +950,16 @@ async fn test_remap(cx: &mut gpui::TestAppContext) {
cx.set_state("ˇ1234\n56789", Mode::Normal);
cx.simulate_keystrokes(["g", "u"]);
cx.assert_state("1234 567ˇ89", Mode::Normal);
// test leaving command
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"g t",
workspace::SendKeystrokes("i space escape".to_string()),
None,
)])
});
cx.set_state("12ˇ34", Mode::Normal);
cx.simulate_keystrokes(["g", "t"]);
cx.assert_state("12ˇ 34", Mode::Normal);
}

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.124.0"
version = "0.124.4"
publish = false
license = "GPL-3.0-or-later"

View File

@@ -1 +1 @@
dev
preview

View File

@@ -71,6 +71,7 @@ impl LspAdapter for AstroLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -122,6 +123,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -84,6 +84,7 @@ impl super::LspAdapter for CLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec![],
})
}
@@ -260,6 +261,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
if clangd_bin.exists() {
Ok(LanguageServerBinary {
path: clangd_bin,
env: None,
arguments: vec![],
})
} else {

View File

@@ -105,6 +105,7 @@ impl super::LspAdapter for ClojureLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec![],
})
}
@@ -118,6 +119,7 @@ impl super::LspAdapter for ClojureLspAdapter {
if binary_path.exists() {
Some(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec![],
})
} else {
@@ -133,6 +135,7 @@ impl super::LspAdapter for ClojureLspAdapter {
if binary_path.exists() {
Some(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec!["--version".into()],
})
} else {

View File

@@ -92,6 +92,7 @@ impl super::LspAdapter for OmniSharpAdapter {
}
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: server_binary_arguments(),
})
}
@@ -136,6 +137,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
env: None,
arguments: server_binary_arguments(),
})
} else {

View File

@@ -72,6 +72,7 @@ impl LspAdapter for CssLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -116,6 +117,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -39,6 +39,7 @@ impl LspAdapter for DartLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "dart".into(),
env: None,
arguments: vec!["language-server".into(), "--protocol=lsp".into()],
})
}

View File

@@ -134,6 +134,7 @@ impl LspAdapter for DenoLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: deno_server_binary_arguments(),
})
}
@@ -220,6 +221,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
if fs::metadata(&binary).await.is_ok() {
return Ok(LanguageServerBinary {
path: binary,
env: None,
arguments: deno_server_binary_arguments(),
});
}

View File

@@ -71,6 +71,7 @@ impl LspAdapter for DockerfileLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -110,6 +111,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -174,6 +174,7 @@ impl LspAdapter for ElixirLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec![],
})
}
@@ -284,6 +285,7 @@ async fn get_cached_server_binary_elixir_ls(
if server_path.exists() {
Some(LanguageServerBinary {
path: server_path,
env: None,
arguments: vec![],
})
} else {
@@ -369,6 +371,7 @@ impl LspAdapter for NextLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec!["--stdio".into()],
})
}
@@ -435,6 +438,7 @@ async fn get_cached_server_binary_next(container_dir: PathBuf) -> Option<Languag
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
env: None,
arguments: Vec::new(),
})
} else {
@@ -476,6 +480,7 @@ impl LspAdapter for LocalLspAdapter {
let path = shellexpand::full(&self.path)?;
Ok(LanguageServerBinary {
path: PathBuf::from(path.deref()),
env: None,
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
@@ -488,6 +493,7 @@ impl LspAdapter for LocalLspAdapter {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
env: None,
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
@@ -496,6 +502,7 @@ impl LspAdapter for LocalLspAdapter {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
env: None,
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}

View File

@@ -75,6 +75,7 @@ impl LspAdapter for ElmLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -134,6 +135,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -41,6 +41,7 @@ impl LspAdapter for ErlangLspAdapter {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "erlang_ls".into(),
env: None,
arguments: vec![],
})
}
@@ -52,6 +53,7 @@ impl LspAdapter for ErlangLspAdapter {
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "erlang_ls".into(),
env: None,
arguments: vec!["--version".into()],
})
}

View File

@@ -81,6 +81,7 @@ impl LspAdapter for GleamLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: server_binary_arguments(),
})
}
@@ -116,6 +117,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
anyhow::Ok(LanguageServerBinary {
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
env: None,
arguments: server_binary_arguments(),
})
})

View File

@@ -58,6 +58,25 @@ impl super::LspAdapter for GoLspAdapter {
Ok(Box::new(version) as Box<_>)
}
fn check_if_user_installed(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<Task<Option<LanguageServerBinary>>> {
let delegate = delegate.clone();
Some(cx.spawn(|cx| async move {
match cx.update(|cx| delegate.which_command(OsString::from("gopls"), cx)) {
Ok(task) => task.await.map(|(path, env)| LanguageServerBinary {
path,
arguments: server_binary_arguments(),
env: Some(env),
}),
Err(_) => None,
}
}))
}
fn will_fetch_server(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
@@ -107,6 +126,7 @@ impl super::LspAdapter for GoLspAdapter {
return Ok(LanguageServerBinary {
path: binary_path.to_path_buf(),
arguments: server_binary_arguments(),
env: None,
});
}
}
@@ -154,6 +174,7 @@ impl super::LspAdapter for GoLspAdapter {
Ok(LanguageServerBinary {
path: binary_path.to_path_buf(),
arguments: server_binary_arguments(),
env: None,
})
}
@@ -372,6 +393,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
Ok(LanguageServerBinary {
path,
arguments: server_binary_arguments(),
env: None,
})
} else {
Err(anyhow!("no cached binary"))

View File

@@ -41,6 +41,7 @@ impl LspAdapter for HaskellLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "haskell-language-server-wrapper".into(),
env: None,
arguments: vec!["lsp".into()],
})
}

View File

@@ -72,6 +72,7 @@ impl LspAdapter for HtmlLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -116,6 +117,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -122,6 +122,7 @@ impl LspAdapter for JsonLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -177,6 +178,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -94,6 +94,7 @@ impl super::LspAdapter for LuaLspAdapter {
}
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: Vec::new(),
})
}
@@ -138,6 +139,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
env: None,
arguments: Vec::new(),
})
} else {

View File

@@ -41,6 +41,7 @@ impl LspAdapter for NuLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "nu".into(),
env: None,
arguments: vec!["--lsp".into()],
})
}

View File

@@ -47,6 +47,7 @@ impl LspAdapter for OCamlLspAdapter {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "ocamllsp".into(),
env: None,
arguments: vec![],
})
}

View File

@@ -69,6 +69,7 @@ impl LspAdapter for IntelephenseLspAdapter {
}
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: intelephense_server_binary_arguments(&server_path),
})
}
@@ -126,6 +127,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: intelephense_server_binary_arguments(&server_path),
})
} else {

View File

@@ -70,6 +70,7 @@ impl LspAdapter for PrismaLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -112,6 +113,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -74,6 +74,7 @@ impl LspAdapter for PurescriptLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -127,6 +128,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -62,6 +62,7 @@ impl LspAdapter for PythonLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -167,6 +168,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Some(LanguageServerBinary {
path: node.binary_path().await.log_err()?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -39,6 +39,7 @@ impl LspAdapter for RubyLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "solargraph".into(),
env: None,
arguments: vec!["stdio".into()],
})
}

View File

@@ -89,6 +89,7 @@ impl LspAdapter for RustLspAdapter {
Ok(LanguageServerBinary {
path: destination_path,
env: None,
arguments: Default::default(),
})
}
@@ -296,6 +297,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
anyhow::Ok(LanguageServerBinary {
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
env: None,
arguments: Default::default(),
})
})

View File

@@ -71,6 +71,7 @@ impl LspAdapter for SvelteLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -148,6 +149,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -73,6 +73,7 @@ impl LspAdapter for TailwindLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -150,6 +151,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -85,6 +85,7 @@ impl LspAdapter for TaploLspAdapter {
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec!["lsp".into(), "stdio".into()],
})
}
@@ -120,6 +121,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
anyhow::Ok(LanguageServerBinary {
path: last.context("no cached binary")?,
env: None,
arguments: Default::default(),
})
})

View File

@@ -97,6 +97,7 @@ impl LspAdapter for TypeScriptLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: typescript_server_binary_arguments(&server_path),
})
}
@@ -192,11 +193,13 @@ async fn get_cached_ts_server_binary(
if new_server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: typescript_server_binary_arguments(&new_server_path),
})
} else if old_server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: typescript_server_binary_arguments(&old_server_path),
})
} else {
@@ -259,7 +262,7 @@ impl LspAdapter for EsLintLspAdapter {
let release = latest_github_release(
"microsoft/vscode-eslint",
false,
false,
true,
delegate.http_client(),
)
.await?;
@@ -307,6 +310,7 @@ impl LspAdapter for EsLintLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: eslint_server_binary_arguments(&server_path),
})
}
@@ -354,6 +358,7 @@ async fn get_cached_eslint_server_binary(
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: eslint_server_binary_arguments(&server_path),
})
})

View File

@@ -41,6 +41,7 @@ impl LspAdapter for UiuaLanguageServer {
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "uiua".into(),
env: None,
arguments: vec!["lsp".into()],
})
}

View File

@@ -118,6 +118,7 @@ impl super::LspAdapter for VueLspAdapter {
*self.typescript_install_path.lock() = Some(ts_path);
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: vue_server_binary_arguments(&server_path),
})
}
@@ -204,6 +205,7 @@ async fn get_cached_server_binary(
Ok((
LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: vue_server_binary_arguments(&server_path),
},
typescript_path,

View File

@@ -74,6 +74,7 @@ impl LspAdapter for YamlLspAdapter {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
@@ -124,6 +125,7 @@ async fn get_cached_server_binary(
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
} else {

View File

@@ -3,10 +3,13 @@ use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use futures::{io::BufReader, StreamExt};
use gpui::{AsyncAppContext, Task};
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use smol::fs;
use std::env::consts::{ARCH, OS};
use std::ffi::OsString;
use std::sync::Arc;
use std::{any::Any, path::PathBuf};
use util::async_maybe;
use util::github::latest_github_release;
@@ -44,6 +47,25 @@ impl LspAdapter for ZlsAdapter {
Ok(Box::new(version) as Box<_>)
}
fn check_if_user_installed(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<Task<Option<LanguageServerBinary>>> {
let delegate = delegate.clone();
Some(cx.spawn(|cx| async move {
match cx.update(|cx| delegate.which_command(OsString::from("zls"), cx)) {
Ok(task) => task.await.map(|(path, env)| LanguageServerBinary {
path,
arguments: vec![],
env: Some(env),
}),
Err(_) => None,
}
}))
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
@@ -75,6 +97,7 @@ impl LspAdapter for ZlsAdapter {
}
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec![],
})
}
@@ -119,6 +142,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
env: None,
arguments: Vec::new(),
})
} else {

View File

@@ -824,8 +824,29 @@ async fn load_login_shell_environment() -> Result<()> {
let shell = env::var("SHELL").context(
"SHELL environment variable is not assigned so we can't source login environment variables",
)?;
// If possible, we want to `cd` in the user's `$HOME` to trigger programs
// such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook
// into shell's `cd` command (and hooks) to manipulate env.
// We do this so that we get the env a user would have when spawning a shell
// in home directory.
let shell_cmd_prefix = std::env::var_os("HOME")
.and_then(|home| home.into_string().ok())
.map(|home| format!("cd {home};"));
// The `exit 0` is the result of hours of debugging, trying to find out
// why running this command here, without `exit 0`, would mess
// up signal process for our process so that `ctrl-c` doesn't work
// anymore.
// We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would
// do that, but it does, and `exit 0` helps.
let shell_cmd = format!(
"{}echo {marker}; /usr/bin/env -0; exit 0;",
shell_cmd_prefix.as_deref().unwrap_or("")
);
let output = Command::new(&shell)
.args(["-l", "-i", "-c", &format!("echo {marker}; /usr/bin/env -0")])
.args(["-l", "-i", "-c", &shell_cmd])
.output()
.await
.context("failed to spawn login shell to source login environment variables")?;