Remove dependencies from the Worktree crate and make it more focused (#12747)
The `worktree` crate mainly provides an in-memory model of a directory and its git repositories. But because it was originally extracted from the Project crate, it also contained lingering bits of code that were outside of that area: * it had a little bit of logic related to buffers (though most buffer management lives in `project`) * it had a *little* bit of logic for storing diagnostics (though the vast majority of LSP and diagnostic logic lives in `project`) * it had a little bit of logic for sending RPC message (though the *receiving* logic for those RPC messages lived in `project`) In this PR, I've moved those concerns entirely to the project crate (where they were already dealt with for the most part), so that the worktree crate can be more focused on its main job, and have fewer dependencies. Worktree no longer depends on `client` or `lsp`. It still depends on `language`, but only because of `impl language::File for worktree::File`. Release Notes: - N/A
This commit is contained in:
parent
00dfd217d8
commit
48581167b7
10 changed files with 554 additions and 689 deletions
|
@ -44,7 +44,7 @@ use language::{
|
|||
markdown, point_to_lsp, prepare_completion_documentation,
|
||||
proto::{
|
||||
deserialize_anchor, deserialize_line_ending, deserialize_version, serialize_anchor,
|
||||
serialize_version, split_operations,
|
||||
serialize_line_ending, serialize_version, split_operations,
|
||||
},
|
||||
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, CodeLabel,
|
||||
ContextProvider, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation,
|
||||
|
@ -121,9 +121,9 @@ pub use task_inventory::{
|
|||
BasicContextProvider, ContextProviderWithTasks, Inventory, TaskSourceKind,
|
||||
};
|
||||
pub use worktree::{
|
||||
DiagnosticSummary, Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId,
|
||||
RepositoryEntry, UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId,
|
||||
WorktreeSettings, FS_WATCH_LATENCY,
|
||||
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, RepositoryEntry,
|
||||
UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings,
|
||||
FS_WATCH_LATENCY,
|
||||
};
|
||||
|
||||
const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
|
||||
|
@ -180,6 +180,18 @@ pub struct Project {
|
|||
next_entry_id: Arc<AtomicUsize>,
|
||||
join_project_response_message_id: u32,
|
||||
next_diagnostic_group_id: usize,
|
||||
diagnostic_summaries:
|
||||
HashMap<WorktreeId, HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>>,
|
||||
diagnostics: HashMap<
|
||||
WorktreeId,
|
||||
HashMap<
|
||||
Arc<Path>,
|
||||
Vec<(
|
||||
LanguageServerId,
|
||||
Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
)>,
|
||||
>,
|
||||
>,
|
||||
user_store: Model<UserStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
client_state: ProjectClientState,
|
||||
|
@ -749,6 +761,8 @@ impl Project {
|
|||
fs,
|
||||
next_entry_id: Default::default(),
|
||||
next_diagnostic_group_id: Default::default(),
|
||||
diagnostics: Default::default(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
supplementary_language_servers: HashMap::default(),
|
||||
language_servers: Default::default(),
|
||||
language_server_ids: HashMap::default(),
|
||||
|
@ -842,8 +856,7 @@ impl Project {
|
|||
// That's because Worktree's identifier is entity id, which should probably be changed.
|
||||
let mut worktrees = Vec::new();
|
||||
for worktree in response.payload.worktrees {
|
||||
let worktree =
|
||||
Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx);
|
||||
let worktree = Worktree::remote(replica_id, worktree, cx);
|
||||
worktrees.push(worktree);
|
||||
}
|
||||
|
||||
|
@ -873,6 +886,8 @@ impl Project {
|
|||
fs,
|
||||
next_entry_id: Default::default(),
|
||||
next_diagnostic_group_id: Default::default(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
diagnostics: Default::default(),
|
||||
client_subscriptions: Default::default(),
|
||||
_subscriptions: vec![
|
||||
cx.on_release(Self::release),
|
||||
|
@ -1733,6 +1748,7 @@ impl Project {
|
|||
let worktrees = this.update(&mut cx, |this, _cx| {
|
||||
this.worktrees().collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
let update_project = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.client.request(proto::UpdateProject {
|
||||
|
@ -1741,14 +1757,49 @@ impl Project {
|
|||
})
|
||||
})?
|
||||
.await;
|
||||
if update_project.log_err().is_some() {
|
||||
if update_project.log_err().is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for worktree in worktrees {
|
||||
worktree.update(&mut cx, |worktree, cx| {
|
||||
let worktree = worktree.as_local_mut().unwrap();
|
||||
worktree.share(project_id, cx).detach_and_log_err(cx)
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
if let Some(summaries) =
|
||||
this.diagnostic_summaries.get(&worktree.id())
|
||||
{
|
||||
for (path, summaries) in summaries {
|
||||
for (&server_id, summary) in summaries {
|
||||
this.client.send(
|
||||
proto::UpdateDiagnosticSummary {
|
||||
project_id,
|
||||
worktree_id: cx.entity_id().as_u64(),
|
||||
summary: Some(
|
||||
summary.to_proto(server_id, path),
|
||||
),
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
worktree.as_local_mut().unwrap().observe_updates(
|
||||
project_id,
|
||||
cx,
|
||||
{
|
||||
let client = client.clone();
|
||||
move |update| {
|
||||
client
|
||||
.request(update)
|
||||
.map(|result| result.is_ok())
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
anyhow::Ok(())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
}
|
||||
LocalProjectUpdate::CreateBufferForPeer { peer_id, buffer_id } => {
|
||||
let buffer = this.update(&mut cx, |this, _| {
|
||||
|
@ -1893,7 +1944,7 @@ impl Project {
|
|||
for worktree_handle in self.worktrees.iter_mut() {
|
||||
if let WorktreeHandle::Strong(worktree) = worktree_handle {
|
||||
let is_visible = worktree.update(cx, |worktree, _| {
|
||||
worktree.as_local_mut().unwrap().unshare();
|
||||
worktree.as_local_mut().unwrap().stop_observing_updates();
|
||||
worktree.is_visible()
|
||||
});
|
||||
if !is_visible {
|
||||
|
@ -2177,23 +2228,47 @@ impl Project {
|
|||
) -> Task<Result<Model<Buffer>>> {
|
||||
let load_buffer = worktree.update(cx, |worktree, cx| {
|
||||
let worktree = worktree.as_local_mut().unwrap();
|
||||
worktree.load_buffer(&path, cx)
|
||||
let file = worktree.load_file(path.as_ref(), cx);
|
||||
let reservation = cx.reserve_model();
|
||||
let buffer_id = BufferId::from(reservation.entity_id().as_non_zero_u64());
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let (file, contents, diff_base) = file.await?;
|
||||
let text_buffer = cx
|
||||
.background_executor()
|
||||
.spawn(async move { text::Buffer::new(0, buffer_id, contents) })
|
||||
.await;
|
||||
cx.insert_model(reservation, |_| {
|
||||
Buffer::build(
|
||||
text_buffer,
|
||||
diff_base,
|
||||
Some(Arc::new(file)),
|
||||
Capability::ReadWrite,
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
fn is_not_found_error(error: &anyhow::Error) -> bool {
|
||||
error
|
||||
.root_cause()
|
||||
.downcast_ref::<io::Error>()
|
||||
.is_some_and(|err| err.kind() == io::ErrorKind::NotFound)
|
||||
}
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let buffer = match load_buffer.await {
|
||||
Ok(buffer) => Ok(buffer),
|
||||
Err(error) if is_not_found_error(&error) => {
|
||||
worktree.update(&mut cx, |worktree, cx| {
|
||||
let worktree = worktree.as_local_mut().unwrap();
|
||||
worktree.new_buffer(path, cx)
|
||||
})
|
||||
}
|
||||
Err(error) if is_not_found_error(&error) => cx.new_model(|cx| {
|
||||
let buffer_id = BufferId::from(cx.entity_id().as_non_zero_u64());
|
||||
let text_buffer = text::Buffer::new(0, buffer_id, "".into());
|
||||
Buffer::build(
|
||||
text_buffer,
|
||||
None,
|
||||
Some(Arc::new(File {
|
||||
worktree,
|
||||
path,
|
||||
mtime: None,
|
||||
entry_id: None,
|
||||
is_local: true,
|
||||
is_deleted: false,
|
||||
is_private: false,
|
||||
})),
|
||||
Capability::ReadWrite,
|
||||
)
|
||||
}),
|
||||
Err(e) => Err(e),
|
||||
}?;
|
||||
this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))??;
|
||||
|
@ -2321,8 +2396,8 @@ impl Project {
|
|||
let worktree = file.worktree.clone();
|
||||
let path = file.path.clone();
|
||||
worktree.update(cx, |worktree, cx| match worktree {
|
||||
Worktree::Local(worktree) => worktree.save_buffer(buffer, path, false, cx),
|
||||
Worktree::Remote(worktree) => worktree.save_buffer(buffer, None, cx),
|
||||
Worktree::Local(worktree) => self.save_local_buffer(&worktree, buffer, path, false, cx),
|
||||
Worktree::Remote(_) => self.save_remote_buffer(buffer, None, cx),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2340,21 +2415,20 @@ impl Project {
|
|||
};
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
if let Some(old_file) = &old_file {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(old_file) = &old_file {
|
||||
this.unregister_buffer_from_language_servers(&buffer, old_file, cx);
|
||||
})?;
|
||||
}
|
||||
worktree
|
||||
.update(&mut cx, |worktree, cx| match worktree {
|
||||
}
|
||||
worktree.update(cx, |worktree, cx| match worktree {
|
||||
Worktree::Local(worktree) => {
|
||||
worktree.save_buffer(buffer.clone(), path.path, true, cx)
|
||||
this.save_local_buffer(worktree, buffer.clone(), path.path, true, cx)
|
||||
}
|
||||
Worktree::Remote(worktree) => {
|
||||
worktree.save_buffer(buffer.clone(), Some(path.to_proto()), cx)
|
||||
Worktree::Remote(_) => {
|
||||
this.save_remote_buffer(buffer.clone(), Some(path.to_proto()), cx)
|
||||
}
|
||||
})?
|
||||
.await?;
|
||||
})
|
||||
})?
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.detect_language_for_buffer(&buffer, cx);
|
||||
|
@ -2364,6 +2438,129 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn save_local_buffer(
|
||||
&self,
|
||||
worktree: &LocalWorktree,
|
||||
buffer_handle: Model<Buffer>,
|
||||
path: Arc<Path>,
|
||||
mut has_changed_file: bool,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<()>> {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
|
||||
let rpc = self.client.clone();
|
||||
let buffer_id: u64 = buffer.remote_id().into();
|
||||
let project_id = self.remote_id();
|
||||
|
||||
if buffer.file().is_some_and(|file| !file.is_created()) {
|
||||
has_changed_file = true;
|
||||
}
|
||||
|
||||
let text = buffer.as_rope().clone();
|
||||
let version = buffer.version();
|
||||
let save = worktree.write_file(path.as_ref(), text, buffer.line_ending(), cx);
|
||||
let fs = Arc::clone(&self.fs);
|
||||
let abs_path = worktree.absolutize(&path);
|
||||
let is_private = worktree.is_path_private(&path);
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let entry = save.await?;
|
||||
let abs_path = abs_path?;
|
||||
let this = this.upgrade().context("worktree dropped")?;
|
||||
|
||||
let (entry_id, mtime, path, is_private) = match entry {
|
||||
Some(entry) => (Some(entry.id), entry.mtime, entry.path, entry.is_private),
|
||||
None => {
|
||||
let metadata = fs
|
||||
.metadata(&abs_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Fetching metadata after saving the excluded buffer {abs_path:?}"
|
||||
)
|
||||
})?
|
||||
.with_context(|| {
|
||||
format!("Excluded buffer {path:?} got removed during saving")
|
||||
})?;
|
||||
(None, Some(metadata.mtime), path, is_private)
|
||||
}
|
||||
};
|
||||
|
||||
if has_changed_file {
|
||||
let new_file = Arc::new(File {
|
||||
entry_id,
|
||||
worktree: this,
|
||||
path,
|
||||
mtime,
|
||||
is_local: true,
|
||||
is_deleted: false,
|
||||
is_private,
|
||||
});
|
||||
|
||||
if let Some(project_id) = project_id {
|
||||
rpc.send(proto::UpdateBufferFile {
|
||||
project_id,
|
||||
buffer_id,
|
||||
file: Some(new_file.to_proto()),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
buffer_handle.update(&mut cx, |buffer, cx| {
|
||||
if has_changed_file {
|
||||
buffer.file_updated(new_file, cx);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Some(project_id) = project_id {
|
||||
rpc.send(proto::BufferSaved {
|
||||
project_id,
|
||||
buffer_id,
|
||||
version: serialize_version(&version),
|
||||
mtime: mtime.map(|time| time.into()),
|
||||
})?;
|
||||
}
|
||||
|
||||
buffer_handle.update(&mut cx, |buffer, cx| {
|
||||
buffer.did_save(version.clone(), mtime, cx);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save_remote_buffer(
|
||||
&self,
|
||||
buffer_handle: Model<Buffer>,
|
||||
new_path: Option<proto::ProjectPath>,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<()>> {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let buffer_id = buffer.remote_id().into();
|
||||
let version = buffer.version();
|
||||
let rpc = self.client.clone();
|
||||
let project_id = self.remote_id();
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let response = rpc
|
||||
.request(proto::SaveBuffer {
|
||||
project_id: project_id.ok_or_else(|| anyhow!("project_id is not set"))?,
|
||||
buffer_id,
|
||||
new_path,
|
||||
version: serialize_version(&version),
|
||||
})
|
||||
.await?;
|
||||
let version = deserialize_version(&response.version);
|
||||
let mtime = response.mtime.map(|mtime| mtime.into());
|
||||
|
||||
buffer_handle.update(&mut cx, |buffer, cx| {
|
||||
buffer.did_save(version.clone(), mtime, cx);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_open_buffer(
|
||||
&mut self,
|
||||
path: &ProjectPath,
|
||||
|
@ -2489,8 +2686,10 @@ impl Project {
|
|||
let language = buffer.language().cloned();
|
||||
let worktree_id = file.worktree_id(cx);
|
||||
|
||||
if let Some(local_worktree) = file.worktree.read(cx).as_local() {
|
||||
for (server_id, diagnostics) in local_worktree.diagnostics_for_path(file.path()) {
|
||||
if let Some(diagnostics) = self.diagnostics.get(&worktree_id) {
|
||||
for (server_id, diagnostics) in
|
||||
diagnostics.get(file.path()).cloned().unwrap_or_default()
|
||||
{
|
||||
self.update_buffer_diagnostics(buffer_handle, server_id, None, diagnostics, cx)
|
||||
.log_err();
|
||||
}
|
||||
|
@ -2729,6 +2928,23 @@ impl Project {
|
|||
.ok();
|
||||
}
|
||||
|
||||
BufferEvent::Reloaded => {
|
||||
if self.is_local() {
|
||||
if let Some(project_id) = self.remote_id() {
|
||||
let buffer = buffer.read(cx);
|
||||
self.client
|
||||
.send(proto::BufferReloaded {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id().to_proto(),
|
||||
version: serialize_version(&buffer.version()),
|
||||
mtime: buffer.saved_mtime().map(|t| t.into()),
|
||||
line_ending: serialize_line_ending(buffer.line_ending()) as i32,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BufferEvent::Edited { .. } => {
|
||||
let buffer = buffer.read(cx);
|
||||
let file = File::from_dyn(buffer.file())?;
|
||||
|
@ -3929,14 +4145,43 @@ impl Project {
|
|||
});
|
||||
}
|
||||
}
|
||||
for worktree in &self.worktrees {
|
||||
if let Some(worktree) = worktree.upgrade() {
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
if let Some(worktree) = worktree.as_local_mut() {
|
||||
worktree.clear_diagnostics_for_language_server(server_id, cx);
|
||||
|
||||
let project_id = self.remote_id();
|
||||
for (worktree_id, summaries) in self.diagnostic_summaries.iter_mut() {
|
||||
summaries.retain(|path, summaries_by_server_id| {
|
||||
if summaries_by_server_id.remove(&server_id).is_some() {
|
||||
if let Some(project_id) = project_id {
|
||||
self.client
|
||||
.send(proto::UpdateDiagnosticSummary {
|
||||
project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
summary: Some(proto::DiagnosticSummary {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
language_server_id: server_id.0 as u64,
|
||||
error_count: 0,
|
||||
warning_count: 0,
|
||||
}),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
});
|
||||
}
|
||||
!summaries_by_server_id.is_empty()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for diagnostics in self.diagnostics.values_mut() {
|
||||
diagnostics.retain(|_, diagnostics_by_server_id| {
|
||||
if let Ok(ix) =
|
||||
diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0)
|
||||
{
|
||||
diagnostics_by_server_id.remove(ix);
|
||||
!diagnostics_by_server_id.is_empty()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.language_server_watched_paths.remove(&server_id);
|
||||
|
@ -4673,10 +4918,13 @@ impl Project {
|
|||
}
|
||||
|
||||
let updated = worktree.update(cx, |worktree, cx| {
|
||||
worktree
|
||||
.as_local_mut()
|
||||
.ok_or_else(|| anyhow!("not a local worktree"))?
|
||||
.update_diagnostics(server_id, project_path.path.clone(), diagnostics, cx)
|
||||
self.update_worktree_diagnostics(
|
||||
worktree.id(),
|
||||
server_id,
|
||||
project_path.path.clone(),
|
||||
diagnostics,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
if updated {
|
||||
cx.emit(Event::DiagnosticsUpdated {
|
||||
|
@ -4687,6 +4935,67 @@ impl Project {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_worktree_diagnostics(
|
||||
&mut self,
|
||||
worktree_id: WorktreeId,
|
||||
server_id: LanguageServerId,
|
||||
worktree_path: Arc<Path>,
|
||||
diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
_: &mut ModelContext<Worktree>,
|
||||
) -> Result<bool> {
|
||||
let summaries_for_tree = self.diagnostic_summaries.entry(worktree_id).or_default();
|
||||
let diagnostics_for_tree = self.diagnostics.entry(worktree_id).or_default();
|
||||
let summaries_by_server_id = summaries_for_tree.entry(worktree_path.clone()).or_default();
|
||||
|
||||
let old_summary = summaries_by_server_id
|
||||
.remove(&server_id)
|
||||
.unwrap_or_default();
|
||||
|
||||
let new_summary = DiagnosticSummary::new(&diagnostics);
|
||||
if new_summary.is_empty() {
|
||||
if let Some(diagnostics_by_server_id) = diagnostics_for_tree.get_mut(&worktree_path) {
|
||||
if let Ok(ix) = diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
|
||||
diagnostics_by_server_id.remove(ix);
|
||||
}
|
||||
if diagnostics_by_server_id.is_empty() {
|
||||
diagnostics_for_tree.remove(&worktree_path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
summaries_by_server_id.insert(server_id, new_summary);
|
||||
let diagnostics_by_server_id = diagnostics_for_tree
|
||||
.entry(worktree_path.clone())
|
||||
.or_default();
|
||||
match diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
|
||||
Ok(ix) => {
|
||||
diagnostics_by_server_id[ix] = (server_id, diagnostics);
|
||||
}
|
||||
Err(ix) => {
|
||||
diagnostics_by_server_id.insert(ix, (server_id, diagnostics));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !old_summary.is_empty() || !new_summary.is_empty() {
|
||||
if let Some(project_id) = self.remote_id() {
|
||||
self.client
|
||||
.send(proto::UpdateDiagnosticSummary {
|
||||
project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
summary: Some(proto::DiagnosticSummary {
|
||||
path: worktree_path.to_string_lossy().to_string(),
|
||||
language_server_id: server_id.0 as u64,
|
||||
error_count: new_summary.error_count as u32,
|
||||
warning_count: new_summary.warning_count as u32,
|
||||
}),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(!old_summary.is_empty() || !new_summary.is_empty())
|
||||
}
|
||||
|
||||
fn update_buffer_diagnostics(
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
|
@ -7453,7 +7762,6 @@ impl Project {
|
|||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Worktree>>> {
|
||||
let fs = self.fs.clone();
|
||||
let client = self.client.clone();
|
||||
let next_entry_id = self.next_entry_id.clone();
|
||||
let path: Arc<Path> = abs_path.as_ref().into();
|
||||
let task = self
|
||||
|
@ -7462,15 +7770,9 @@ impl Project {
|
|||
.or_insert_with(|| {
|
||||
cx.spawn(move |project, mut cx| {
|
||||
async move {
|
||||
let worktree = Worktree::local(
|
||||
client.clone(),
|
||||
path.clone(),
|
||||
visible,
|
||||
fs,
|
||||
next_entry_id,
|
||||
&mut cx,
|
||||
)
|
||||
.await;
|
||||
let worktree =
|
||||
Worktree::local(path.clone(), visible, fs, next_entry_id, &mut cx)
|
||||
.await;
|
||||
|
||||
project.update(&mut cx, |project, _| {
|
||||
project.loading_local_worktrees.remove(&path);
|
||||
|
@ -7503,6 +7805,9 @@ impl Project {
|
|||
}
|
||||
|
||||
pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
|
||||
self.diagnostics.remove(&id_to_remove);
|
||||
self.diagnostic_summaries.remove(&id_to_remove);
|
||||
|
||||
let mut servers_to_remove = HashMap::default();
|
||||
let mut servers_to_preserve = HashSet::default();
|
||||
for ((worktree_id, server_name), &server_id) in &self.language_server_ids {
|
||||
|
@ -8128,23 +8433,34 @@ impl Project {
|
|||
include_ignored: bool,
|
||||
cx: &'a AppContext,
|
||||
) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
|
||||
self.visible_worktrees(cx).flat_map(move |worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
let worktree_id = worktree.id();
|
||||
worktree
|
||||
.diagnostic_summaries()
|
||||
.filter_map(move |(path, server_id, summary)| {
|
||||
if include_ignored
|
||||
|| worktree
|
||||
.entry_for_path(path.as_ref())
|
||||
.map_or(false, |entry| !entry.is_ignored)
|
||||
{
|
||||
Some((ProjectPath { worktree_id, path }, server_id, summary))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
self.visible_worktrees(cx)
|
||||
.filter_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
Some((worktree, self.diagnostic_summaries.get(&worktree.id())?))
|
||||
})
|
||||
.flat_map(move |(worktree, summaries)| {
|
||||
let worktree_id = worktree.id();
|
||||
summaries
|
||||
.iter()
|
||||
.filter(move |(path, _)| {
|
||||
include_ignored
|
||||
|| worktree
|
||||
.entry_for_path(path.as_ref())
|
||||
.map_or(false, |entry| !entry.is_ignored)
|
||||
})
|
||||
.flat_map(move |(path, summaries)| {
|
||||
summaries.iter().map(move |(server_id, summary)| {
|
||||
(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: path.clone(),
|
||||
},
|
||||
*server_id,
|
||||
*summary,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn disk_based_diagnostics_started(
|
||||
|
@ -8805,23 +9121,41 @@ impl Project {
|
|||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
|
||||
if let Some(summary) = envelope.payload.summary {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: Path::new(&summary.path).into(),
|
||||
};
|
||||
worktree.update(cx, |worktree, _| {
|
||||
worktree
|
||||
.as_remote_mut()
|
||||
.unwrap()
|
||||
.update_diagnostic_summary(project_path.path.clone(), &summary);
|
||||
});
|
||||
cx.emit(Event::DiagnosticsUpdated {
|
||||
language_server_id: LanguageServerId(summary.language_server_id as usize),
|
||||
path: project_path,
|
||||
});
|
||||
if let Some(message) = envelope.payload.summary {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: Path::new(&message.path).into(),
|
||||
};
|
||||
let path = project_path.path.clone();
|
||||
let server_id = LanguageServerId(message.language_server_id as usize);
|
||||
let summary = DiagnosticSummary {
|
||||
error_count: message.error_count as usize,
|
||||
warning_count: message.warning_count as usize,
|
||||
};
|
||||
|
||||
if summary.is_empty() {
|
||||
if let Some(worktree_summaries) =
|
||||
this.diagnostic_summaries.get_mut(&worktree_id)
|
||||
{
|
||||
if let Some(summaries) = worktree_summaries.get_mut(&path) {
|
||||
summaries.remove(&server_id);
|
||||
if summaries.is_empty() {
|
||||
worktree_summaries.remove(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.diagnostic_summaries
|
||||
.entry(worktree_id)
|
||||
.or_default()
|
||||
.entry(path)
|
||||
.or_default()
|
||||
.insert(server_id, summary);
|
||||
}
|
||||
cx.emit(Event::DiagnosticsUpdated {
|
||||
language_server_id: LanguageServerId(message.language_server_id as usize),
|
||||
path: project_path,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})?
|
||||
|
@ -10229,7 +10563,6 @@ impl Project {
|
|||
cx: &mut ModelContext<Project>,
|
||||
) -> Result<()> {
|
||||
let replica_id = self.replica_id();
|
||||
let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
|
||||
|
||||
let mut old_worktrees_by_id = self
|
||||
.worktrees
|
||||
|
@ -10246,8 +10579,7 @@ impl Project {
|
|||
{
|
||||
self.worktrees.push(WorktreeHandle::Strong(old_worktree));
|
||||
} else {
|
||||
let worktree =
|
||||
Worktree::remote(remote_id, replica_id, worktree, self.client.clone(), cx);
|
||||
let worktree = Worktree::remote(replica_id, worktree, cx);
|
||||
let _ = self.add_worktree(&worktree, cx);
|
||||
}
|
||||
}
|
||||
|
@ -11502,6 +11834,13 @@ async fn wait_for_loading_buffer(
|
|||
}
|
||||
}
|
||||
|
||||
fn is_not_found_error(error: &anyhow::Error) -> bool {
|
||||
error
|
||||
.root_cause()
|
||||
.downcast_ref::<io::Error>()
|
||||
.is_some_and(|err| err.kind() == io::ErrorKind::NotFound)
|
||||
}
|
||||
|
||||
fn include_text(server: &lsp::LanguageServer) -> bool {
|
||||
server
|
||||
.capabilities()
|
||||
|
@ -11740,3 +12079,47 @@ fn deserialize_location(
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)]
|
||||
pub struct DiagnosticSummary {
|
||||
pub error_count: usize,
|
||||
pub warning_count: usize,
|
||||
}
|
||||
|
||||
impl DiagnosticSummary {
|
||||
pub fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
|
||||
let mut this = Self {
|
||||
error_count: 0,
|
||||
warning_count: 0,
|
||||
};
|
||||
|
||||
for entry in diagnostics {
|
||||
if entry.diagnostic.is_primary {
|
||||
match entry.diagnostic.severity {
|
||||
DiagnosticSeverity::ERROR => this.error_count += 1,
|
||||
DiagnosticSeverity::WARNING => this.warning_count += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.error_count == 0 && self.warning_count == 0
|
||||
}
|
||||
|
||||
pub fn to_proto(
|
||||
&self,
|
||||
language_server_id: LanguageServerId,
|
||||
path: &Path,
|
||||
) -> proto::DiagnosticSummary {
|
||||
proto::DiagnosticSummary {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
language_server_id: language_server_id.0 as u64,
|
||||
error_count: self.error_count as u32,
|
||||
warning_count: self.warning_count as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue