Add support for projects managed with Yarn (#13644)

TODO:
- [ ] File a PR with Yarn to add Zed to the list of supported IDEs.

Fixes: https://github.com/zed-industries/zed/issues/10107
Fixes: https://github.com/zed-industries/zed/issues/13706
Release Notes:

- Improved experience in projects using Yarn. Run `yarn dlx
@yarnpkg/sdks base` in the root of your project in order to elevate your
experience.

---------

Co-authored-by: Saurabh <79586784+m4saurabh@users.noreply.github.com>
This commit is contained in:
Piotr Osiewicz 2024-07-11 14:56:07 +02:00 committed by GitHub
parent 291d64c803
commit 2727f55772
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 320 additions and 24 deletions

View file

@ -11,6 +11,7 @@ pub mod terminals;
#[cfg(test)]
mod project_tests;
pub mod search_history;
mod yarn;
use anyhow::{anyhow, bail, Context as _, Result};
use async_trait::async_trait;
@ -116,6 +117,7 @@ use util::{
NumericPrefixWithSuffix, ResultExt, TryFutureExt as _,
};
use worktree::{CreatedEntry, RemoteWorktreeClient, Snapshot, Traversal};
use yarn::YarnPathStore;
pub use fs::*;
pub use language::Location;
@ -231,6 +233,7 @@ pub struct Project {
dev_server_project_id: Option<client::DevServerProjectId>,
search_history: SearchHistory,
snippets: Model<SnippetProvider>,
yarn: Model<YarnPathStore>,
}
pub enum LanguageServerToQuery {
@ -728,6 +731,7 @@ impl Project {
let global_snippets_dir = paths::config_dir().join("snippets");
let snippets =
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
let yarn = YarnPathStore::new(fs.clone(), cx);
Self {
worktrees: Vec::new(),
worktrees_reordered: false,
@ -753,6 +757,7 @@ impl Project {
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
_maintain_workspace_config: Self::maintain_workspace_config(cx),
active_entry: None,
yarn,
snippets,
languages,
client,
@ -853,6 +858,7 @@ impl Project {
let global_snippets_dir = paths::config_dir().join("snippets");
let snippets =
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
let yarn = YarnPathStore::new(fs.clone(), cx);
// BIG CAUTION NOTE: The order in which we initialize fields here matters and it should match what's done in Self::local.
// Otherwise, you might run into issues where worktree id on remote is different than what's on local host.
// That's because Worktree's identifier is entity id, which should probably be changed.
@ -891,6 +897,7 @@ impl Project {
languages,
user_store: user_store.clone(),
snippets,
yarn,
fs,
next_entry_id: Default::default(),
next_diagnostic_group_id: Default::default(),
@ -2163,23 +2170,51 @@ impl Project {
/// LanguageServerName is owned, because it is inserted into a map
pub fn open_local_buffer_via_lsp(
&mut self,
abs_path: lsp::Url,
mut abs_path: lsp::Url,
language_server_id: LanguageServerId,
language_server_name: LanguageServerName,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Buffer>>> {
cx.spawn(move |this, mut cx| async move {
// Escape percent-encoded string.
let current_scheme = abs_path.scheme().to_owned();
let _ = abs_path.set_scheme("file");
let abs_path = abs_path
.to_file_path()
.map_err(|_| anyhow!("can't convert URI to path"))?;
let (worktree, relative_path) = if let Some(result) =
this.update(&mut cx, |this, cx| this.find_local_worktree(&abs_path, cx))?
{
result
let p = abs_path.clone();
let yarn_worktree = this
.update(&mut cx, move |this, cx| {
this.yarn.update(cx, |_, cx| {
cx.spawn(|this, mut cx| async move {
let t = this
.update(&mut cx, |this, cx| {
this.process_path(&p, &current_scheme, cx)
})
.ok()?;
t.await
})
})
})?
.await;
let (worktree_root_target, known_relative_path) =
if let Some((zip_root, relative_path)) = yarn_worktree {
(zip_root, Some(relative_path))
} else {
(Arc::<Path>::from(abs_path.as_path()), None)
};
let (worktree, relative_path) = if let Some(result) = this
.update(&mut cx, |this, cx| {
this.find_local_worktree(&worktree_root_target, cx)
})? {
let relative_path =
known_relative_path.unwrap_or_else(|| Arc::<Path>::from(result.1));
(result.0, relative_path)
} else {
let worktree = this
.update(&mut cx, |this, cx| {
this.create_local_worktree(&abs_path, false, cx)
this.create_local_worktree(&worktree_root_target, false, cx)
})?
.await?;
this.update(&mut cx, |this, cx| {
@ -2189,12 +2224,17 @@ impl Project {
);
})
.ok();
(worktree, PathBuf::new())
let worktree_root = worktree.update(&mut cx, |this, _| this.abs_path())?;
let relative_path = if let Some(known_path) = known_relative_path {
known_path
} else {
abs_path.strip_prefix(worktree_root)?.into()
};
(worktree, relative_path)
};
let project_path = ProjectPath {
worktree_id: worktree.update(&mut cx, |worktree, _| worktree.id())?,
path: relative_path.into(),
path: relative_path,
};
this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))?
.await