1pub mod fs;
2mod ignore;
3pub mod worktree;
4
5use anyhow::{anyhow, Result};
6use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
7use clock::ReplicaId;
8use collections::{hash_map, HashMap, HashSet};
9use futures::Future;
10use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
11use gpui::{
12 AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
13 WeakModelHandle,
14};
15use language::{
16 point_from_lsp,
17 proto::{deserialize_anchor, serialize_anchor},
18 range_from_lsp, Bias, Buffer, Diagnostic, DiagnosticEntry, File as _, Language,
19 LanguageRegistry, PointUtf16, ToOffset,
20};
21use lsp::{DiagnosticSeverity, LanguageServer};
22use postage::{prelude::Stream, watch};
23use smol::block_on;
24use std::{
25 convert::TryInto,
26 ops::Range,
27 path::{Path, PathBuf},
28 sync::{atomic::AtomicBool, Arc},
29};
30use util::{post_inc, ResultExt, TryFutureExt as _};
31
32pub use fs::*;
33pub use worktree::*;
34
35pub struct Project {
36 worktrees: Vec<WorktreeHandle>,
37 active_entry: Option<ProjectEntry>,
38 languages: Arc<LanguageRegistry>,
39 language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
40 client: Arc<client::Client>,
41 user_store: ModelHandle<UserStore>,
42 fs: Arc<dyn Fs>,
43 client_state: ProjectClientState,
44 collaborators: HashMap<PeerId, Collaborator>,
45 subscriptions: Vec<client::Subscription>,
46 language_servers_with_diagnostics_running: isize,
47 open_buffers: HashMap<usize, WeakModelHandle<Buffer>>,
48 loading_buffers: HashMap<
49 ProjectPath,
50 postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
51 >,
52 shared_buffers: HashMap<PeerId, HashMap<u64, ModelHandle<Buffer>>>,
53}
54
55enum WorktreeHandle {
56 Strong(ModelHandle<Worktree>),
57 Weak(WeakModelHandle<Worktree>),
58}
59
60enum ProjectClientState {
61 Local {
62 is_shared: bool,
63 remote_id_tx: watch::Sender<Option<u64>>,
64 remote_id_rx: watch::Receiver<Option<u64>>,
65 _maintain_remote_id_task: Task<Option<()>>,
66 },
67 Remote {
68 sharing_has_stopped: bool,
69 remote_id: u64,
70 replica_id: ReplicaId,
71 },
72}
73
74#[derive(Clone, Debug)]
75pub struct Collaborator {
76 pub user: Arc<User>,
77 pub peer_id: PeerId,
78 pub replica_id: ReplicaId,
79}
80
81#[derive(Clone, Debug, PartialEq)]
82pub enum Event {
83 ActiveEntryChanged(Option<ProjectEntry>),
84 WorktreeRemoved(WorktreeId),
85 DiskBasedDiagnosticsStarted,
86 DiskBasedDiagnosticsUpdated,
87 DiskBasedDiagnosticsFinished,
88 DiagnosticsUpdated(ProjectPath),
89}
90
91#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
92pub struct ProjectPath {
93 pub worktree_id: WorktreeId,
94 pub path: Arc<Path>,
95}
96
97#[derive(Clone, Debug, Default, PartialEq)]
98pub struct DiagnosticSummary {
99 pub error_count: usize,
100 pub warning_count: usize,
101 pub info_count: usize,
102 pub hint_count: usize,
103}
104
105#[derive(Debug)]
106pub struct Definition {
107 pub target_buffer: ModelHandle<Buffer>,
108 pub target_range: Range<language::Anchor>,
109}
110
111impl DiagnosticSummary {
112 fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
113 let mut this = Self {
114 error_count: 0,
115 warning_count: 0,
116 info_count: 0,
117 hint_count: 0,
118 };
119
120 for entry in diagnostics {
121 if entry.diagnostic.is_primary {
122 match entry.diagnostic.severity {
123 DiagnosticSeverity::ERROR => this.error_count += 1,
124 DiagnosticSeverity::WARNING => this.warning_count += 1,
125 DiagnosticSeverity::INFORMATION => this.info_count += 1,
126 DiagnosticSeverity::HINT => this.hint_count += 1,
127 _ => {}
128 }
129 }
130 }
131
132 this
133 }
134
135 pub fn to_proto(&self, path: Arc<Path>) -> proto::DiagnosticSummary {
136 proto::DiagnosticSummary {
137 path: path.to_string_lossy().to_string(),
138 error_count: self.error_count as u32,
139 warning_count: self.warning_count as u32,
140 info_count: self.info_count as u32,
141 hint_count: self.hint_count as u32,
142 }
143 }
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
147pub struct ProjectEntry {
148 pub worktree_id: WorktreeId,
149 pub entry_id: usize,
150}
151
152impl Project {
153 pub fn local(
154 client: Arc<Client>,
155 user_store: ModelHandle<UserStore>,
156 languages: Arc<LanguageRegistry>,
157 fs: Arc<dyn Fs>,
158 cx: &mut MutableAppContext,
159 ) -> ModelHandle<Self> {
160 cx.add_model(|cx: &mut ModelContext<Self>| {
161 let (remote_id_tx, remote_id_rx) = watch::channel();
162 let _maintain_remote_id_task = cx.spawn_weak({
163 let rpc = client.clone();
164 move |this, mut cx| {
165 async move {
166 let mut status = rpc.status();
167 while let Some(status) = status.recv().await {
168 if let Some(this) = this.upgrade(&cx) {
169 let remote_id = if let client::Status::Connected { .. } = status {
170 let response = rpc.request(proto::RegisterProject {}).await?;
171 Some(response.project_id)
172 } else {
173 None
174 };
175
176 if let Some(project_id) = remote_id {
177 let mut registrations = Vec::new();
178 this.update(&mut cx, |this, cx| {
179 for worktree in this.worktrees(cx).collect::<Vec<_>>() {
180 registrations.push(worktree.update(
181 cx,
182 |worktree, cx| {
183 let worktree = worktree.as_local_mut().unwrap();
184 worktree.register(project_id, cx)
185 },
186 ));
187 }
188 });
189 for registration in registrations {
190 registration.await?;
191 }
192 }
193 this.update(&mut cx, |this, cx| this.set_remote_id(remote_id, cx));
194 }
195 }
196 Ok(())
197 }
198 .log_err()
199 }
200 });
201
202 Self {
203 worktrees: Default::default(),
204 collaborators: Default::default(),
205 open_buffers: Default::default(),
206 loading_buffers: Default::default(),
207 shared_buffers: Default::default(),
208 client_state: ProjectClientState::Local {
209 is_shared: false,
210 remote_id_tx,
211 remote_id_rx,
212 _maintain_remote_id_task,
213 },
214 subscriptions: Vec::new(),
215 active_entry: None,
216 languages,
217 client,
218 user_store,
219 fs,
220 language_servers_with_diagnostics_running: 0,
221 language_servers: Default::default(),
222 }
223 })
224 }
225
226 pub async fn remote(
227 remote_id: u64,
228 client: Arc<Client>,
229 user_store: ModelHandle<UserStore>,
230 languages: Arc<LanguageRegistry>,
231 fs: Arc<dyn Fs>,
232 cx: &mut AsyncAppContext,
233 ) -> Result<ModelHandle<Self>> {
234 client.authenticate_and_connect(&cx).await?;
235
236 let response = client
237 .request(proto::JoinProject {
238 project_id: remote_id,
239 })
240 .await?;
241
242 let replica_id = response.replica_id as ReplicaId;
243
244 let mut worktrees = Vec::new();
245 for worktree in response.worktrees {
246 let (worktree, load_task) = cx
247 .update(|cx| Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx));
248 worktrees.push(worktree);
249 load_task.detach();
250 }
251
252 let user_ids = response
253 .collaborators
254 .iter()
255 .map(|peer| peer.user_id)
256 .collect();
257 user_store
258 .update(cx, |user_store, cx| user_store.load_users(user_ids, cx))
259 .await?;
260 let mut collaborators = HashMap::default();
261 for message in response.collaborators {
262 let collaborator = Collaborator::from_proto(message, &user_store, cx).await?;
263 collaborators.insert(collaborator.peer_id, collaborator);
264 }
265
266 Ok(cx.add_model(|cx| {
267 let mut this = Self {
268 worktrees: Vec::new(),
269 open_buffers: Default::default(),
270 loading_buffers: Default::default(),
271 shared_buffers: Default::default(),
272 active_entry: None,
273 collaborators,
274 languages,
275 user_store,
276 fs,
277 subscriptions: vec![
278 client.subscribe_to_entity(remote_id, cx, Self::handle_unshare_project),
279 client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
280 client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
281 client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree),
282 client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree),
283 client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
284 client.subscribe_to_entity(
285 remote_id,
286 cx,
287 Self::handle_update_diagnostic_summary,
288 ),
289 client.subscribe_to_entity(
290 remote_id,
291 cx,
292 Self::handle_disk_based_diagnostics_updating,
293 ),
294 client.subscribe_to_entity(
295 remote_id,
296 cx,
297 Self::handle_disk_based_diagnostics_updated,
298 ),
299 client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
300 client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer_file),
301 client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_reloaded),
302 client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
303 ],
304 client,
305 client_state: ProjectClientState::Remote {
306 sharing_has_stopped: false,
307 remote_id,
308 replica_id,
309 },
310 language_servers_with_diagnostics_running: 0,
311 language_servers: Default::default(),
312 };
313 for worktree in worktrees {
314 this.add_worktree(&worktree, cx);
315 }
316 this
317 }))
318 }
319
320 fn set_remote_id(&mut self, remote_id: Option<u64>, cx: &mut ModelContext<Self>) {
321 if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state {
322 *remote_id_tx.borrow_mut() = remote_id;
323 }
324
325 self.subscriptions.clear();
326 if let Some(remote_id) = remote_id {
327 let client = &self.client;
328 self.subscriptions.extend([
329 client.subscribe_to_entity(remote_id, cx, Self::handle_open_buffer),
330 client.subscribe_to_entity(remote_id, cx, Self::handle_close_buffer),
331 client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
332 client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
333 client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
334 client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
335 client.subscribe_to_entity(remote_id, cx, Self::handle_save_buffer),
336 client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
337 client.subscribe_to_entity(remote_id, cx, Self::handle_format_buffer),
338 client.subscribe_to_entity(remote_id, cx, Self::handle_get_completions),
339 client.subscribe_to_entity(
340 remote_id,
341 cx,
342 Self::handle_apply_additional_edits_for_completion,
343 ),
344 client.subscribe_to_entity(remote_id, cx, Self::handle_get_definition),
345 ]);
346 }
347 }
348
349 pub fn remote_id(&self) -> Option<u64> {
350 match &self.client_state {
351 ProjectClientState::Local { remote_id_rx, .. } => *remote_id_rx.borrow(),
352 ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
353 }
354 }
355
356 pub fn next_remote_id(&self) -> impl Future<Output = u64> {
357 let mut id = None;
358 let mut watch = None;
359 match &self.client_state {
360 ProjectClientState::Local { remote_id_rx, .. } => watch = Some(remote_id_rx.clone()),
361 ProjectClientState::Remote { remote_id, .. } => id = Some(*remote_id),
362 }
363
364 async move {
365 if let Some(id) = id {
366 return id;
367 }
368 let mut watch = watch.unwrap();
369 loop {
370 let id = *watch.borrow();
371 if let Some(id) = id {
372 return id;
373 }
374 watch.recv().await;
375 }
376 }
377 }
378
379 pub fn replica_id(&self) -> ReplicaId {
380 match &self.client_state {
381 ProjectClientState::Local { .. } => 0,
382 ProjectClientState::Remote { replica_id, .. } => *replica_id,
383 }
384 }
385
386 pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
387 &self.collaborators
388 }
389
390 pub fn worktrees<'a>(
391 &'a self,
392 cx: &'a AppContext,
393 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
394 self.worktrees
395 .iter()
396 .filter_map(move |worktree| worktree.upgrade(cx))
397 }
398
399 pub fn worktree_for_id(
400 &self,
401 id: WorktreeId,
402 cx: &AppContext,
403 ) -> Option<ModelHandle<Worktree>> {
404 self.worktrees(cx)
405 .find(|worktree| worktree.read(cx).id() == id)
406 }
407
408 pub fn share(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
409 let rpc = self.client.clone();
410 cx.spawn(|this, mut cx| async move {
411 let project_id = this.update(&mut cx, |this, _| {
412 if let ProjectClientState::Local {
413 is_shared,
414 remote_id_rx,
415 ..
416 } = &mut this.client_state
417 {
418 *is_shared = true;
419 remote_id_rx
420 .borrow()
421 .ok_or_else(|| anyhow!("no project id"))
422 } else {
423 Err(anyhow!("can't share a remote project"))
424 }
425 })?;
426
427 rpc.request(proto::ShareProject { project_id }).await?;
428 let mut tasks = Vec::new();
429 this.update(&mut cx, |this, cx| {
430 for worktree in this.worktrees(cx).collect::<Vec<_>>() {
431 worktree.update(cx, |worktree, cx| {
432 let worktree = worktree.as_local_mut().unwrap();
433 tasks.push(worktree.share(project_id, cx));
434 });
435 }
436 });
437 for task in tasks {
438 task.await?;
439 }
440 this.update(&mut cx, |_, cx| cx.notify());
441 Ok(())
442 })
443 }
444
445 pub fn unshare(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
446 let rpc = self.client.clone();
447 cx.spawn(|this, mut cx| async move {
448 let project_id = this.update(&mut cx, |this, _| {
449 if let ProjectClientState::Local {
450 is_shared,
451 remote_id_rx,
452 ..
453 } = &mut this.client_state
454 {
455 *is_shared = false;
456 remote_id_rx
457 .borrow()
458 .ok_or_else(|| anyhow!("no project id"))
459 } else {
460 Err(anyhow!("can't share a remote project"))
461 }
462 })?;
463
464 rpc.send(proto::UnshareProject { project_id }).await?;
465 this.update(&mut cx, |this, cx| {
466 this.collaborators.clear();
467 this.shared_buffers.clear();
468 for worktree in this.worktrees(cx).collect::<Vec<_>>() {
469 worktree.update(cx, |worktree, _| {
470 worktree.as_local_mut().unwrap().unshare();
471 });
472 }
473 cx.notify()
474 });
475 Ok(())
476 })
477 }
478
479 pub fn is_read_only(&self) -> bool {
480 match &self.client_state {
481 ProjectClientState::Local { .. } => false,
482 ProjectClientState::Remote {
483 sharing_has_stopped,
484 ..
485 } => *sharing_has_stopped,
486 }
487 }
488
489 pub fn is_local(&self) -> bool {
490 match &self.client_state {
491 ProjectClientState::Local { .. } => true,
492 ProjectClientState::Remote { .. } => false,
493 }
494 }
495
496 pub fn open_buffer(
497 &mut self,
498 path: impl Into<ProjectPath>,
499 cx: &mut ModelContext<Self>,
500 ) -> Task<Result<ModelHandle<Buffer>>> {
501 let project_path = path.into();
502 let worktree = if let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) {
503 worktree
504 } else {
505 return Task::ready(Err(anyhow!("no such worktree")));
506 };
507
508 // If there is already a buffer for the given path, then return it.
509 let existing_buffer = self.get_open_buffer(&project_path, cx);
510 if let Some(existing_buffer) = existing_buffer {
511 return Task::ready(Ok(existing_buffer));
512 }
513
514 let mut loading_watch = match self.loading_buffers.entry(project_path.clone()) {
515 // If the given path is already being loaded, then wait for that existing
516 // task to complete and return the same buffer.
517 hash_map::Entry::Occupied(e) => e.get().clone(),
518
519 // Otherwise, record the fact that this path is now being loaded.
520 hash_map::Entry::Vacant(entry) => {
521 let (mut tx, rx) = postage::watch::channel();
522 entry.insert(rx.clone());
523
524 let load_buffer = if worktree.read(cx).is_local() {
525 self.open_local_buffer(&project_path.path, &worktree, cx)
526 } else {
527 self.open_remote_buffer(&project_path.path, &worktree, cx)
528 };
529
530 cx.spawn(move |this, mut cx| async move {
531 let load_result = load_buffer.await;
532 *tx.borrow_mut() = Some(this.update(&mut cx, |this, _| {
533 // Record the fact that the buffer is no longer loading.
534 this.loading_buffers.remove(&project_path);
535 let buffer = load_result.map_err(Arc::new)?;
536 Ok(buffer)
537 }));
538 })
539 .detach();
540 rx
541 }
542 };
543
544 cx.foreground().spawn(async move {
545 loop {
546 if let Some(result) = loading_watch.borrow().as_ref() {
547 match result {
548 Ok(buffer) => return Ok(buffer.clone()),
549 Err(error) => return Err(anyhow!("{}", error)),
550 }
551 }
552 loading_watch.recv().await;
553 }
554 })
555 }
556
557 fn open_local_buffer(
558 &mut self,
559 path: &Arc<Path>,
560 worktree: &ModelHandle<Worktree>,
561 cx: &mut ModelContext<Self>,
562 ) -> Task<Result<ModelHandle<Buffer>>> {
563 let load_buffer = worktree.update(cx, |worktree, cx| {
564 let worktree = worktree.as_local_mut().unwrap();
565 worktree.load_buffer(path, cx)
566 });
567 let worktree = worktree.downgrade();
568 cx.spawn(|this, mut cx| async move {
569 let buffer = load_buffer.await?;
570 let worktree = worktree
571 .upgrade(&cx)
572 .ok_or_else(|| anyhow!("worktree was removed"))?;
573 this.update(&mut cx, |this, cx| {
574 this.register_buffer(&buffer, Some(&worktree), cx)
575 })?;
576 Ok(buffer)
577 })
578 }
579
580 fn open_remote_buffer(
581 &mut self,
582 path: &Arc<Path>,
583 worktree: &ModelHandle<Worktree>,
584 cx: &mut ModelContext<Self>,
585 ) -> Task<Result<ModelHandle<Buffer>>> {
586 let rpc = self.client.clone();
587 let project_id = self.remote_id().unwrap();
588 let remote_worktree_id = worktree.read(cx).id();
589 let path = path.clone();
590 let path_string = path.to_string_lossy().to_string();
591 cx.spawn(|this, mut cx| async move {
592 let response = rpc
593 .request(proto::OpenBuffer {
594 project_id,
595 worktree_id: remote_worktree_id.to_proto(),
596 path: path_string,
597 })
598 .await?;
599 let buffer = response.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
600 this.update(&mut cx, |this, cx| {
601 this.deserialize_remote_buffer(buffer, cx)
602 })
603 })
604 }
605
606 pub fn save_buffer_as(
607 &self,
608 buffer: ModelHandle<Buffer>,
609 abs_path: PathBuf,
610 cx: &mut ModelContext<Project>,
611 ) -> Task<Result<()>> {
612 let worktree_task = self.find_or_create_local_worktree(&abs_path, false, cx);
613 cx.spawn(|this, mut cx| async move {
614 let (worktree, path) = worktree_task.await?;
615 worktree
616 .update(&mut cx, |worktree, cx| {
617 worktree
618 .as_local_mut()
619 .unwrap()
620 .save_buffer_as(buffer.clone(), path, cx)
621 })
622 .await?;
623 this.update(&mut cx, |this, cx| {
624 this.assign_language_to_buffer(&buffer, Some(&worktree), cx);
625 });
626 Ok(())
627 })
628 }
629
630 #[cfg(any(test, feature = "test-support"))]
631 pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &AppContext) -> bool {
632 let path = path.into();
633 if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
634 self.open_buffers.iter().any(|(_, buffer)| {
635 if let Some(buffer) = buffer.upgrade(cx) {
636 if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
637 if file.worktree == worktree && file.path() == &path.path {
638 return true;
639 }
640 }
641 }
642 false
643 })
644 } else {
645 false
646 }
647 }
648
649 fn get_open_buffer(
650 &mut self,
651 path: &ProjectPath,
652 cx: &mut ModelContext<Self>,
653 ) -> Option<ModelHandle<Buffer>> {
654 let mut result = None;
655 let worktree = self.worktree_for_id(path.worktree_id, cx)?;
656 self.open_buffers.retain(|_, buffer| {
657 if let Some(buffer) = buffer.upgrade(cx) {
658 if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
659 if file.worktree == worktree && file.path() == &path.path {
660 result = Some(buffer);
661 }
662 }
663 true
664 } else {
665 false
666 }
667 });
668 result
669 }
670
671 fn register_buffer(
672 &mut self,
673 buffer: &ModelHandle<Buffer>,
674 worktree: Option<&ModelHandle<Worktree>>,
675 cx: &mut ModelContext<Self>,
676 ) -> Result<()> {
677 if self
678 .open_buffers
679 .insert(buffer.read(cx).remote_id() as usize, buffer.downgrade())
680 .is_some()
681 {
682 return Err(anyhow!("registered the same buffer twice"));
683 }
684 self.assign_language_to_buffer(&buffer, worktree, cx);
685 Ok(())
686 }
687
688 fn assign_language_to_buffer(
689 &mut self,
690 buffer: &ModelHandle<Buffer>,
691 worktree: Option<&ModelHandle<Worktree>>,
692 cx: &mut ModelContext<Self>,
693 ) -> Option<()> {
694 let (path, full_path) = {
695 let file = buffer.read(cx).file()?;
696 (file.path().clone(), file.full_path(cx))
697 };
698
699 // If the buffer has a language, set it and start/assign the language server
700 if let Some(language) = self.languages.select_language(&full_path) {
701 buffer.update(cx, |buffer, cx| {
702 buffer.set_language(Some(language.clone()), cx);
703 });
704
705 // For local worktrees, start a language server if needed.
706 // Also assign the language server and any previously stored diagnostics to the buffer.
707 if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
708 let worktree_id = local_worktree.id();
709 let worktree_abs_path = local_worktree.abs_path().clone();
710
711 let language_server = match self
712 .language_servers
713 .entry((worktree_id, language.name().to_string()))
714 {
715 hash_map::Entry::Occupied(e) => Some(e.get().clone()),
716 hash_map::Entry::Vacant(e) => Self::start_language_server(
717 self.client.clone(),
718 language.clone(),
719 &worktree_abs_path,
720 cx,
721 )
722 .map(|server| e.insert(server).clone()),
723 };
724
725 buffer.update(cx, |buffer, cx| {
726 buffer.set_language_server(language_server, cx);
727 });
728 }
729 }
730
731 if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
732 if let Some(diagnostics) = local_worktree.diagnostics_for_path(&path) {
733 buffer.update(cx, |buffer, cx| {
734 buffer.update_diagnostics(None, diagnostics, cx).log_err();
735 });
736 }
737 }
738
739 None
740 }
741
742 fn start_language_server(
743 rpc: Arc<Client>,
744 language: Arc<Language>,
745 worktree_path: &Path,
746 cx: &mut ModelContext<Self>,
747 ) -> Option<Arc<LanguageServer>> {
748 enum LspEvent {
749 DiagnosticsStart,
750 DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
751 DiagnosticsFinish,
752 }
753
754 let language_server = language
755 .start_server(worktree_path, cx)
756 .log_err()
757 .flatten()?;
758 let disk_based_sources = language
759 .disk_based_diagnostic_sources()
760 .cloned()
761 .unwrap_or_default();
762 let disk_based_diagnostics_progress_token =
763 language.disk_based_diagnostics_progress_token().cloned();
764 let has_disk_based_diagnostic_progress_token =
765 disk_based_diagnostics_progress_token.is_some();
766 let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
767
768 // Listen for `PublishDiagnostics` notifications.
769 language_server
770 .on_notification::<lsp::notification::PublishDiagnostics, _>({
771 let diagnostics_tx = diagnostics_tx.clone();
772 move |params| {
773 if !has_disk_based_diagnostic_progress_token {
774 block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
775 }
776 block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))).ok();
777 if !has_disk_based_diagnostic_progress_token {
778 block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
779 }
780 }
781 })
782 .detach();
783
784 // Listen for `Progress` notifications. Send an event when the language server
785 // transitions between running jobs and not running any jobs.
786 let mut running_jobs_for_this_server: i32 = 0;
787 language_server
788 .on_notification::<lsp::notification::Progress, _>(move |params| {
789 let token = match params.token {
790 lsp::NumberOrString::Number(_) => None,
791 lsp::NumberOrString::String(token) => Some(token),
792 };
793
794 if token == disk_based_diagnostics_progress_token {
795 match params.value {
796 lsp::ProgressParamsValue::WorkDone(progress) => match progress {
797 lsp::WorkDoneProgress::Begin(_) => {
798 running_jobs_for_this_server += 1;
799 if running_jobs_for_this_server == 1 {
800 block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
801 }
802 }
803 lsp::WorkDoneProgress::End(_) => {
804 running_jobs_for_this_server -= 1;
805 if running_jobs_for_this_server == 0 {
806 block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
807 }
808 }
809 _ => {}
810 },
811 }
812 }
813 })
814 .detach();
815
816 // Process all the LSP events.
817 cx.spawn_weak(|this, mut cx| async move {
818 while let Ok(message) = diagnostics_rx.recv().await {
819 let this = cx.read(|cx| this.upgrade(cx))?;
820 match message {
821 LspEvent::DiagnosticsStart => {
822 let send = this.update(&mut cx, |this, cx| {
823 this.disk_based_diagnostics_started(cx);
824 this.remote_id().map(|project_id| {
825 rpc.send(proto::DiskBasedDiagnosticsUpdating { project_id })
826 })
827 });
828 if let Some(send) = send {
829 send.await.log_err();
830 }
831 }
832 LspEvent::DiagnosticsUpdate(mut params) => {
833 language.process_diagnostics(&mut params);
834 this.update(&mut cx, |this, cx| {
835 this.update_diagnostics(params, &disk_based_sources, cx)
836 .log_err();
837 });
838 }
839 LspEvent::DiagnosticsFinish => {
840 let send = this.update(&mut cx, |this, cx| {
841 this.disk_based_diagnostics_finished(cx);
842 this.remote_id().map(|project_id| {
843 rpc.send(proto::DiskBasedDiagnosticsUpdated { project_id })
844 })
845 });
846 if let Some(send) = send {
847 send.await.log_err();
848 }
849 }
850 }
851 }
852 Some(())
853 })
854 .detach();
855
856 Some(language_server)
857 }
858
859 pub fn update_diagnostics(
860 &mut self,
861 params: lsp::PublishDiagnosticsParams,
862 disk_based_sources: &HashSet<String>,
863 cx: &mut ModelContext<Self>,
864 ) -> Result<()> {
865 let abs_path = params
866 .uri
867 .to_file_path()
868 .map_err(|_| anyhow!("URI is not a file"))?;
869 let mut next_group_id = 0;
870 let mut diagnostics = Vec::default();
871 let mut primary_diagnostic_group_ids = HashMap::default();
872 let mut sources_by_group_id = HashMap::default();
873 let mut supporting_diagnostic_severities = HashMap::default();
874 for diagnostic in ¶ms.diagnostics {
875 let source = diagnostic.source.as_ref();
876 let code = diagnostic.code.as_ref().map(|code| match code {
877 lsp::NumberOrString::Number(code) => code.to_string(),
878 lsp::NumberOrString::String(code) => code.clone(),
879 });
880 let range = range_from_lsp(diagnostic.range);
881 let is_supporting = diagnostic
882 .related_information
883 .as_ref()
884 .map_or(false, |infos| {
885 infos.iter().any(|info| {
886 primary_diagnostic_group_ids.contains_key(&(
887 source,
888 code.clone(),
889 range_from_lsp(info.location.range),
890 ))
891 })
892 });
893
894 if is_supporting {
895 if let Some(severity) = diagnostic.severity {
896 supporting_diagnostic_severities
897 .insert((source, code.clone(), range), severity);
898 }
899 } else {
900 let group_id = post_inc(&mut next_group_id);
901 let is_disk_based =
902 source.map_or(false, |source| disk_based_sources.contains(source));
903
904 sources_by_group_id.insert(group_id, source);
905 primary_diagnostic_group_ids
906 .insert((source, code.clone(), range.clone()), group_id);
907
908 diagnostics.push(DiagnosticEntry {
909 range,
910 diagnostic: Diagnostic {
911 code: code.clone(),
912 severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
913 message: diagnostic.message.clone(),
914 group_id,
915 is_primary: true,
916 is_valid: true,
917 is_disk_based,
918 },
919 });
920 if let Some(infos) = &diagnostic.related_information {
921 for info in infos {
922 if info.location.uri == params.uri && !info.message.is_empty() {
923 let range = range_from_lsp(info.location.range);
924 diagnostics.push(DiagnosticEntry {
925 range,
926 diagnostic: Diagnostic {
927 code: code.clone(),
928 severity: DiagnosticSeverity::INFORMATION,
929 message: info.message.clone(),
930 group_id,
931 is_primary: false,
932 is_valid: true,
933 is_disk_based,
934 },
935 });
936 }
937 }
938 }
939 }
940 }
941
942 for entry in &mut diagnostics {
943 let diagnostic = &mut entry.diagnostic;
944 if !diagnostic.is_primary {
945 let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap();
946 if let Some(&severity) = supporting_diagnostic_severities.get(&(
947 source,
948 diagnostic.code.clone(),
949 entry.range.clone(),
950 )) {
951 diagnostic.severity = severity;
952 }
953 }
954 }
955
956 self.update_diagnostic_entries(abs_path, params.version, diagnostics, cx)?;
957 Ok(())
958 }
959
960 pub fn update_diagnostic_entries(
961 &mut self,
962 abs_path: PathBuf,
963 version: Option<i32>,
964 diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
965 cx: &mut ModelContext<Project>,
966 ) -> Result<(), anyhow::Error> {
967 let (worktree, relative_path) = self
968 .find_local_worktree(&abs_path, cx)
969 .ok_or_else(|| anyhow!("no worktree found for diagnostics"))?;
970 let project_path = ProjectPath {
971 worktree_id: worktree.read(cx).id(),
972 path: relative_path.into(),
973 };
974
975 for buffer in self.open_buffers.values() {
976 if let Some(buffer) = buffer.upgrade(cx) {
977 if buffer
978 .read(cx)
979 .file()
980 .map_or(false, |file| *file.path() == project_path.path)
981 {
982 buffer.update(cx, |buffer, cx| {
983 buffer.update_diagnostics(version, diagnostics.clone(), cx)
984 })?;
985 break;
986 }
987 }
988 }
989 worktree.update(cx, |worktree, cx| {
990 worktree
991 .as_local_mut()
992 .ok_or_else(|| anyhow!("not a local worktree"))?
993 .update_diagnostics(project_path.path.clone(), diagnostics, cx)
994 })?;
995 cx.emit(Event::DiagnosticsUpdated(project_path));
996 Ok(())
997 }
998
999 pub fn definition<T: ToOffset>(
1000 &self,
1001 source_buffer_handle: &ModelHandle<Buffer>,
1002 position: T,
1003 cx: &mut ModelContext<Self>,
1004 ) -> Task<Result<Vec<Definition>>> {
1005 let source_buffer_handle = source_buffer_handle.clone();
1006 let source_buffer = source_buffer_handle.read(cx);
1007 let worktree;
1008 let buffer_abs_path;
1009 if let Some(file) = File::from_dyn(source_buffer.file()) {
1010 worktree = file.worktree.clone();
1011 buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
1012 } else {
1013 return Task::ready(Err(anyhow!("buffer does not belong to any worktree")));
1014 };
1015
1016 if worktree.read(cx).as_local().is_some() {
1017 let point = source_buffer.offset_to_point_utf16(position.to_offset(source_buffer));
1018 let buffer_abs_path = buffer_abs_path.unwrap();
1019 let lang_name;
1020 let lang_server;
1021 if let Some(lang) = source_buffer.language() {
1022 lang_name = lang.name().to_string();
1023 if let Some(server) = self
1024 .language_servers
1025 .get(&(worktree.read(cx).id(), lang_name.clone()))
1026 {
1027 lang_server = server.clone();
1028 } else {
1029 return Task::ready(Err(anyhow!("buffer does not have a language server")));
1030 };
1031 } else {
1032 return Task::ready(Err(anyhow!("buffer does not have a language")));
1033 }
1034
1035 cx.spawn(|this, mut cx| async move {
1036 let response = lang_server
1037 .request::<lsp::request::GotoDefinition>(lsp::GotoDefinitionParams {
1038 text_document_position_params: lsp::TextDocumentPositionParams {
1039 text_document: lsp::TextDocumentIdentifier::new(
1040 lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
1041 ),
1042 position: lsp::Position::new(point.row, point.column),
1043 },
1044 work_done_progress_params: Default::default(),
1045 partial_result_params: Default::default(),
1046 })
1047 .await?;
1048
1049 let mut definitions = Vec::new();
1050 if let Some(response) = response {
1051 let mut unresolved_locations = Vec::new();
1052 match response {
1053 lsp::GotoDefinitionResponse::Scalar(loc) => {
1054 unresolved_locations.push((loc.uri, loc.range));
1055 }
1056 lsp::GotoDefinitionResponse::Array(locs) => {
1057 unresolved_locations.extend(locs.into_iter().map(|l| (l.uri, l.range)));
1058 }
1059 lsp::GotoDefinitionResponse::Link(links) => {
1060 unresolved_locations.extend(
1061 links
1062 .into_iter()
1063 .map(|l| (l.target_uri, l.target_selection_range)),
1064 );
1065 }
1066 }
1067
1068 for (target_uri, target_range) in unresolved_locations {
1069 let abs_path = target_uri
1070 .to_file_path()
1071 .map_err(|_| anyhow!("invalid target path"))?;
1072
1073 let (worktree, relative_path) = if let Some(result) =
1074 this.read_with(&cx, |this, cx| this.find_local_worktree(&abs_path, cx))
1075 {
1076 result
1077 } else {
1078 let worktree = this
1079 .update(&mut cx, |this, cx| {
1080 this.create_local_worktree(&abs_path, true, cx)
1081 })
1082 .await?;
1083 this.update(&mut cx, |this, cx| {
1084 this.language_servers.insert(
1085 (worktree.read(cx).id(), lang_name.clone()),
1086 lang_server.clone(),
1087 );
1088 });
1089 (worktree, PathBuf::new())
1090 };
1091
1092 let project_path = ProjectPath {
1093 worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
1094 path: relative_path.into(),
1095 };
1096 let target_buffer_handle = this
1097 .update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
1098 .await?;
1099 cx.read(|cx| {
1100 let target_buffer = target_buffer_handle.read(cx);
1101 let target_start = target_buffer
1102 .clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
1103 let target_end = target_buffer
1104 .clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
1105 definitions.push(Definition {
1106 target_buffer: target_buffer_handle,
1107 target_range: target_buffer.anchor_after(target_start)
1108 ..target_buffer.anchor_before(target_end),
1109 });
1110 });
1111 }
1112 }
1113
1114 Ok(definitions)
1115 })
1116 } else if let Some(project_id) = self.remote_id() {
1117 let client = self.client.clone();
1118 let request = proto::GetDefinition {
1119 project_id,
1120 buffer_id: source_buffer.remote_id(),
1121 position: Some(serialize_anchor(&source_buffer.anchor_before(position))),
1122 };
1123 cx.spawn(|this, mut cx| async move {
1124 let response = client.request(request).await?;
1125 this.update(&mut cx, |this, cx| {
1126 let mut definitions = Vec::new();
1127 for definition in response.definitions {
1128 let target_buffer = this.deserialize_remote_buffer(
1129 definition.buffer.ok_or_else(|| anyhow!("missing buffer"))?,
1130 cx,
1131 )?;
1132 let target_start = definition
1133 .target_start
1134 .and_then(deserialize_anchor)
1135 .ok_or_else(|| anyhow!("missing target start"))?;
1136 let target_end = definition
1137 .target_end
1138 .and_then(deserialize_anchor)
1139 .ok_or_else(|| anyhow!("missing target end"))?;
1140 definitions.push(Definition {
1141 target_buffer,
1142 target_range: target_start..target_end,
1143 })
1144 }
1145
1146 Ok(definitions)
1147 })
1148 })
1149 } else {
1150 Task::ready(Err(anyhow!("project does not have a remote id")))
1151 }
1152 }
1153
1154 pub fn find_or_create_local_worktree(
1155 &self,
1156 abs_path: impl AsRef<Path>,
1157 weak: bool,
1158 cx: &mut ModelContext<Self>,
1159 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
1160 let abs_path = abs_path.as_ref();
1161 if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) {
1162 Task::ready(Ok((tree.clone(), relative_path.into())))
1163 } else {
1164 let worktree = self.create_local_worktree(abs_path, weak, cx);
1165 cx.foreground()
1166 .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
1167 }
1168 }
1169
1170 fn find_local_worktree(
1171 &self,
1172 abs_path: &Path,
1173 cx: &AppContext,
1174 ) -> Option<(ModelHandle<Worktree>, PathBuf)> {
1175 for tree in self.worktrees(cx) {
1176 if let Some(relative_path) = tree
1177 .read(cx)
1178 .as_local()
1179 .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
1180 {
1181 return Some((tree.clone(), relative_path.into()));
1182 }
1183 }
1184 None
1185 }
1186
1187 pub fn is_shared(&self) -> bool {
1188 match &self.client_state {
1189 ProjectClientState::Local { is_shared, .. } => *is_shared,
1190 ProjectClientState::Remote { .. } => false,
1191 }
1192 }
1193
1194 fn create_local_worktree(
1195 &self,
1196 abs_path: impl AsRef<Path>,
1197 weak: bool,
1198 cx: &mut ModelContext<Self>,
1199 ) -> Task<Result<ModelHandle<Worktree>>> {
1200 let fs = self.fs.clone();
1201 let client = self.client.clone();
1202 let path = Arc::from(abs_path.as_ref());
1203 cx.spawn(|project, mut cx| async move {
1204 let worktree = Worktree::local(client.clone(), path, weak, fs, &mut cx).await?;
1205
1206 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
1207 project.add_worktree(&worktree, cx);
1208 (project.remote_id(), project.is_shared())
1209 });
1210
1211 if let Some(project_id) = remote_project_id {
1212 worktree
1213 .update(&mut cx, |worktree, cx| {
1214 worktree.as_local_mut().unwrap().register(project_id, cx)
1215 })
1216 .await?;
1217 if is_shared {
1218 worktree
1219 .update(&mut cx, |worktree, cx| {
1220 worktree.as_local_mut().unwrap().share(project_id, cx)
1221 })
1222 .await?;
1223 }
1224 }
1225
1226 Ok(worktree)
1227 })
1228 }
1229
1230 pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext<Self>) {
1231 self.worktrees.retain(|worktree| {
1232 worktree
1233 .upgrade(cx)
1234 .map_or(false, |w| w.read(cx).id() != id)
1235 });
1236 cx.notify();
1237 }
1238
1239 fn add_worktree(&mut self, worktree: &ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
1240 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
1241 if worktree.read(cx).is_local() {
1242 cx.subscribe(&worktree, |this, worktree, _, cx| {
1243 this.update_local_worktree_buffers(worktree, cx);
1244 })
1245 .detach();
1246 }
1247
1248 let push_weak_handle = {
1249 let worktree = worktree.read(cx);
1250 worktree.is_local() && worktree.is_weak()
1251 };
1252 if push_weak_handle {
1253 cx.observe_release(&worktree, |this, cx| {
1254 this.worktrees
1255 .retain(|worktree| worktree.upgrade(cx).is_some());
1256 cx.notify();
1257 })
1258 .detach();
1259 self.worktrees
1260 .push(WorktreeHandle::Weak(worktree.downgrade()));
1261 } else {
1262 self.worktrees
1263 .push(WorktreeHandle::Strong(worktree.clone()));
1264 }
1265 cx.notify();
1266 }
1267
1268 fn update_local_worktree_buffers(
1269 &mut self,
1270 worktree_handle: ModelHandle<Worktree>,
1271 cx: &mut ModelContext<Self>,
1272 ) {
1273 let snapshot = worktree_handle.read(cx).snapshot();
1274 let mut buffers_to_delete = Vec::new();
1275 for (buffer_id, buffer) in &self.open_buffers {
1276 if let Some(buffer) = buffer.upgrade(cx) {
1277 buffer.update(cx, |buffer, cx| {
1278 if let Some(old_file) = File::from_dyn(buffer.file()) {
1279 if old_file.worktree != worktree_handle {
1280 return;
1281 }
1282
1283 let new_file = if let Some(entry) = old_file
1284 .entry_id
1285 .and_then(|entry_id| snapshot.entry_for_id(entry_id))
1286 {
1287 File {
1288 is_local: true,
1289 entry_id: Some(entry.id),
1290 mtime: entry.mtime,
1291 path: entry.path.clone(),
1292 worktree: worktree_handle.clone(),
1293 }
1294 } else if let Some(entry) =
1295 snapshot.entry_for_path(old_file.path().as_ref())
1296 {
1297 File {
1298 is_local: true,
1299 entry_id: Some(entry.id),
1300 mtime: entry.mtime,
1301 path: entry.path.clone(),
1302 worktree: worktree_handle.clone(),
1303 }
1304 } else {
1305 File {
1306 is_local: true,
1307 entry_id: None,
1308 path: old_file.path().clone(),
1309 mtime: old_file.mtime(),
1310 worktree: worktree_handle.clone(),
1311 }
1312 };
1313
1314 if let Some(project_id) = self.remote_id() {
1315 let client = self.client.clone();
1316 let message = proto::UpdateBufferFile {
1317 project_id,
1318 buffer_id: *buffer_id as u64,
1319 file: Some(new_file.to_proto()),
1320 };
1321 cx.foreground()
1322 .spawn(async move { client.send(message).await })
1323 .detach_and_log_err(cx);
1324 }
1325 buffer.file_updated(Box::new(new_file), cx).detach();
1326 }
1327 });
1328 } else {
1329 buffers_to_delete.push(*buffer_id);
1330 }
1331 }
1332
1333 for buffer_id in buffers_to_delete {
1334 self.open_buffers.remove(&buffer_id);
1335 }
1336 }
1337
1338 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
1339 let new_active_entry = entry.and_then(|project_path| {
1340 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
1341 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
1342 Some(ProjectEntry {
1343 worktree_id: project_path.worktree_id,
1344 entry_id: entry.id,
1345 })
1346 });
1347 if new_active_entry != self.active_entry {
1348 self.active_entry = new_active_entry;
1349 cx.emit(Event::ActiveEntryChanged(new_active_entry));
1350 }
1351 }
1352
1353 pub fn is_running_disk_based_diagnostics(&self) -> bool {
1354 self.language_servers_with_diagnostics_running > 0
1355 }
1356
1357 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
1358 let mut summary = DiagnosticSummary::default();
1359 for (_, path_summary) in self.diagnostic_summaries(cx) {
1360 summary.error_count += path_summary.error_count;
1361 summary.warning_count += path_summary.warning_count;
1362 summary.info_count += path_summary.info_count;
1363 summary.hint_count += path_summary.hint_count;
1364 }
1365 summary
1366 }
1367
1368 pub fn diagnostic_summaries<'a>(
1369 &'a self,
1370 cx: &'a AppContext,
1371 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
1372 self.worktrees(cx).flat_map(move |worktree| {
1373 let worktree = worktree.read(cx);
1374 let worktree_id = worktree.id();
1375 worktree
1376 .diagnostic_summaries()
1377 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
1378 })
1379 }
1380
1381 pub fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext<Self>) {
1382 self.language_servers_with_diagnostics_running += 1;
1383 if self.language_servers_with_diagnostics_running == 1 {
1384 cx.emit(Event::DiskBasedDiagnosticsStarted);
1385 }
1386 }
1387
1388 pub fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext<Self>) {
1389 cx.emit(Event::DiskBasedDiagnosticsUpdated);
1390 self.language_servers_with_diagnostics_running -= 1;
1391 if self.language_servers_with_diagnostics_running == 0 {
1392 cx.emit(Event::DiskBasedDiagnosticsFinished);
1393 }
1394 }
1395
1396 pub fn active_entry(&self) -> Option<ProjectEntry> {
1397 self.active_entry
1398 }
1399
1400 // RPC message handlers
1401
1402 fn handle_unshare_project(
1403 &mut self,
1404 _: TypedEnvelope<proto::UnshareProject>,
1405 _: Arc<Client>,
1406 cx: &mut ModelContext<Self>,
1407 ) -> Result<()> {
1408 if let ProjectClientState::Remote {
1409 sharing_has_stopped,
1410 ..
1411 } = &mut self.client_state
1412 {
1413 *sharing_has_stopped = true;
1414 self.collaborators.clear();
1415 cx.notify();
1416 Ok(())
1417 } else {
1418 unreachable!()
1419 }
1420 }
1421
1422 fn handle_add_collaborator(
1423 &mut self,
1424 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
1425 _: Arc<Client>,
1426 cx: &mut ModelContext<Self>,
1427 ) -> Result<()> {
1428 let user_store = self.user_store.clone();
1429 let collaborator = envelope
1430 .payload
1431 .collaborator
1432 .take()
1433 .ok_or_else(|| anyhow!("empty collaborator"))?;
1434
1435 cx.spawn(|this, mut cx| {
1436 async move {
1437 let collaborator =
1438 Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
1439 this.update(&mut cx, |this, cx| {
1440 this.collaborators
1441 .insert(collaborator.peer_id, collaborator);
1442 cx.notify();
1443 });
1444 Ok(())
1445 }
1446 .log_err()
1447 })
1448 .detach();
1449
1450 Ok(())
1451 }
1452
1453 fn handle_remove_collaborator(
1454 &mut self,
1455 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
1456 _: Arc<Client>,
1457 cx: &mut ModelContext<Self>,
1458 ) -> Result<()> {
1459 let peer_id = PeerId(envelope.payload.peer_id);
1460 let replica_id = self
1461 .collaborators
1462 .remove(&peer_id)
1463 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
1464 .replica_id;
1465 self.shared_buffers.remove(&peer_id);
1466 for (_, buffer) in &self.open_buffers {
1467 if let Some(buffer) = buffer.upgrade(cx) {
1468 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
1469 }
1470 }
1471 cx.notify();
1472 Ok(())
1473 }
1474
1475 fn handle_share_worktree(
1476 &mut self,
1477 envelope: TypedEnvelope<proto::ShareWorktree>,
1478 client: Arc<Client>,
1479 cx: &mut ModelContext<Self>,
1480 ) -> Result<()> {
1481 let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
1482 let replica_id = self.replica_id();
1483 let worktree = envelope
1484 .payload
1485 .worktree
1486 .ok_or_else(|| anyhow!("invalid worktree"))?;
1487 let (worktree, load_task) = Worktree::remote(remote_id, replica_id, worktree, client, cx);
1488 self.add_worktree(&worktree, cx);
1489 load_task.detach();
1490 Ok(())
1491 }
1492
1493 fn handle_unregister_worktree(
1494 &mut self,
1495 envelope: TypedEnvelope<proto::UnregisterWorktree>,
1496 _: Arc<Client>,
1497 cx: &mut ModelContext<Self>,
1498 ) -> Result<()> {
1499 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1500 self.remove_worktree(worktree_id, cx);
1501 Ok(())
1502 }
1503
1504 fn handle_update_worktree(
1505 &mut self,
1506 envelope: TypedEnvelope<proto::UpdateWorktree>,
1507 _: Arc<Client>,
1508 cx: &mut ModelContext<Self>,
1509 ) -> Result<()> {
1510 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1511 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1512 worktree.update(cx, |worktree, cx| {
1513 let worktree = worktree.as_remote_mut().unwrap();
1514 worktree.update_from_remote(envelope, cx)
1515 })?;
1516 }
1517 Ok(())
1518 }
1519
1520 fn handle_update_diagnostic_summary(
1521 &mut self,
1522 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
1523 _: Arc<Client>,
1524 cx: &mut ModelContext<Self>,
1525 ) -> Result<()> {
1526 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1527 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1528 if let Some(summary) = envelope.payload.summary {
1529 let project_path = ProjectPath {
1530 worktree_id,
1531 path: Path::new(&summary.path).into(),
1532 };
1533 worktree.update(cx, |worktree, _| {
1534 worktree
1535 .as_remote_mut()
1536 .unwrap()
1537 .update_diagnostic_summary(project_path.path.clone(), &summary);
1538 });
1539 cx.emit(Event::DiagnosticsUpdated(project_path));
1540 }
1541 }
1542 Ok(())
1543 }
1544
1545 fn handle_disk_based_diagnostics_updating(
1546 &mut self,
1547 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
1548 _: Arc<Client>,
1549 cx: &mut ModelContext<Self>,
1550 ) -> Result<()> {
1551 self.disk_based_diagnostics_started(cx);
1552 Ok(())
1553 }
1554
1555 fn handle_disk_based_diagnostics_updated(
1556 &mut self,
1557 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
1558 _: Arc<Client>,
1559 cx: &mut ModelContext<Self>,
1560 ) -> Result<()> {
1561 self.disk_based_diagnostics_finished(cx);
1562 Ok(())
1563 }
1564
1565 pub fn handle_update_buffer(
1566 &mut self,
1567 envelope: TypedEnvelope<proto::UpdateBuffer>,
1568 _: Arc<Client>,
1569 cx: &mut ModelContext<Self>,
1570 ) -> Result<()> {
1571 let payload = envelope.payload.clone();
1572 let buffer_id = payload.buffer_id as usize;
1573 let ops = payload
1574 .operations
1575 .into_iter()
1576 .map(|op| language::proto::deserialize_operation(op))
1577 .collect::<Result<Vec<_>, _>>()?;
1578 if let Some(buffer) = self.open_buffers.get_mut(&buffer_id) {
1579 if let Some(buffer) = buffer.upgrade(cx) {
1580 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
1581 }
1582 }
1583 Ok(())
1584 }
1585
1586 pub fn handle_update_buffer_file(
1587 &mut self,
1588 envelope: TypedEnvelope<proto::UpdateBufferFile>,
1589 _: Arc<Client>,
1590 cx: &mut ModelContext<Self>,
1591 ) -> Result<()> {
1592 let payload = envelope.payload.clone();
1593 let buffer_id = payload.buffer_id as usize;
1594 let file = payload.file.ok_or_else(|| anyhow!("invalid file"))?;
1595 let worktree = self
1596 .worktree_for_id(WorktreeId::from_proto(file.worktree_id), cx)
1597 .ok_or_else(|| anyhow!("no such worktree"))?;
1598 let file = File::from_proto(file, worktree.clone(), cx)?;
1599 let buffer = self
1600 .open_buffers
1601 .get_mut(&buffer_id)
1602 .and_then(|b| b.upgrade(cx))
1603 .ok_or_else(|| anyhow!("no such buffer"))?;
1604 buffer.update(cx, |buffer, cx| {
1605 buffer.file_updated(Box::new(file), cx).detach();
1606 });
1607
1608 Ok(())
1609 }
1610
1611 pub fn handle_save_buffer(
1612 &mut self,
1613 envelope: TypedEnvelope<proto::SaveBuffer>,
1614 rpc: Arc<Client>,
1615 cx: &mut ModelContext<Self>,
1616 ) -> Result<()> {
1617 let sender_id = envelope.original_sender_id()?;
1618 let project_id = self.remote_id().ok_or_else(|| anyhow!("not connected"))?;
1619 let buffer = self
1620 .shared_buffers
1621 .get(&sender_id)
1622 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1623 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1624 let receipt = envelope.receipt();
1625 let buffer_id = envelope.payload.buffer_id;
1626 let save = cx.spawn(|_, mut cx| async move {
1627 buffer.update(&mut cx, |buffer, cx| buffer.save(cx)).await
1628 });
1629
1630 cx.background()
1631 .spawn(
1632 async move {
1633 let (version, mtime) = save.await?;
1634
1635 rpc.respond(
1636 receipt,
1637 proto::BufferSaved {
1638 project_id,
1639 buffer_id,
1640 version: (&version).into(),
1641 mtime: Some(mtime.into()),
1642 },
1643 )
1644 .await?;
1645
1646 Ok(())
1647 }
1648 .log_err(),
1649 )
1650 .detach();
1651 Ok(())
1652 }
1653
1654 pub fn handle_format_buffer(
1655 &mut self,
1656 envelope: TypedEnvelope<proto::FormatBuffer>,
1657 rpc: Arc<Client>,
1658 cx: &mut ModelContext<Self>,
1659 ) -> Result<()> {
1660 let receipt = envelope.receipt();
1661 let sender_id = envelope.original_sender_id()?;
1662 let buffer = self
1663 .shared_buffers
1664 .get(&sender_id)
1665 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1666 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1667 cx.spawn(|_, mut cx| async move {
1668 let format = buffer.update(&mut cx, |buffer, cx| buffer.format(cx)).await;
1669 // We spawn here in order to enqueue the sending of `Ack` *after* transmission of edits
1670 // associated with formatting.
1671 cx.spawn(|_| async move {
1672 match format {
1673 Ok(()) => rpc.respond(receipt, proto::Ack {}).await?,
1674 Err(error) => {
1675 rpc.respond_with_error(
1676 receipt,
1677 proto::Error {
1678 message: error.to_string(),
1679 },
1680 )
1681 .await?
1682 }
1683 }
1684 Ok::<_, anyhow::Error>(())
1685 })
1686 .await
1687 .log_err();
1688 })
1689 .detach();
1690 Ok(())
1691 }
1692
1693 fn handle_get_completions(
1694 &mut self,
1695 envelope: TypedEnvelope<proto::GetCompletions>,
1696 rpc: Arc<Client>,
1697 cx: &mut ModelContext<Self>,
1698 ) -> Result<()> {
1699 let receipt = envelope.receipt();
1700 let sender_id = envelope.original_sender_id()?;
1701 let buffer = self
1702 .shared_buffers
1703 .get(&sender_id)
1704 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1705 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1706 let position = envelope
1707 .payload
1708 .position
1709 .and_then(language::proto::deserialize_anchor)
1710 .ok_or_else(|| anyhow!("invalid position"))?;
1711 cx.spawn(|_, mut cx| async move {
1712 match buffer
1713 .update(&mut cx, |buffer, cx| buffer.completions(position, cx))
1714 .await
1715 {
1716 Ok(completions) => {
1717 rpc.respond(
1718 receipt,
1719 proto::GetCompletionsResponse {
1720 completions: completions
1721 .iter()
1722 .map(language::proto::serialize_completion)
1723 .collect(),
1724 },
1725 )
1726 .await
1727 }
1728 Err(error) => {
1729 rpc.respond_with_error(
1730 receipt,
1731 proto::Error {
1732 message: error.to_string(),
1733 },
1734 )
1735 .await
1736 }
1737 }
1738 })
1739 .detach_and_log_err(cx);
1740 Ok(())
1741 }
1742
1743 fn handle_apply_additional_edits_for_completion(
1744 &mut self,
1745 envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
1746 rpc: Arc<Client>,
1747 cx: &mut ModelContext<Self>,
1748 ) -> Result<()> {
1749 let receipt = envelope.receipt();
1750 let sender_id = envelope.original_sender_id()?;
1751 let buffer = self
1752 .shared_buffers
1753 .get(&sender_id)
1754 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1755 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1756 let language = buffer.read(cx).language();
1757 let completion = language::proto::deserialize_completion(
1758 envelope
1759 .payload
1760 .completion
1761 .ok_or_else(|| anyhow!("invalid position"))?,
1762 language,
1763 )?;
1764 cx.spawn(|_, mut cx| async move {
1765 match buffer
1766 .update(&mut cx, |buffer, cx| {
1767 buffer.apply_additional_edits_for_completion(completion, false, cx)
1768 })
1769 .await
1770 {
1771 Ok(edit_ids) => {
1772 rpc.respond(
1773 receipt,
1774 proto::ApplyCompletionAdditionalEditsResponse {
1775 additional_edits: edit_ids
1776 .into_iter()
1777 .map(|edit_id| proto::AdditionalEdit {
1778 replica_id: edit_id.replica_id as u32,
1779 local_timestamp: edit_id.value,
1780 })
1781 .collect(),
1782 },
1783 )
1784 .await
1785 }
1786 Err(error) => {
1787 rpc.respond_with_error(
1788 receipt,
1789 proto::Error {
1790 message: error.to_string(),
1791 },
1792 )
1793 .await
1794 }
1795 }
1796 })
1797 .detach_and_log_err(cx);
1798 Ok(())
1799 }
1800
1801 pub fn handle_get_definition(
1802 &mut self,
1803 envelope: TypedEnvelope<proto::GetDefinition>,
1804 rpc: Arc<Client>,
1805 cx: &mut ModelContext<Self>,
1806 ) -> Result<()> {
1807 let receipt = envelope.receipt();
1808 let sender_id = envelope.original_sender_id()?;
1809 let source_buffer = self
1810 .shared_buffers
1811 .get(&sender_id)
1812 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1813 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1814 let position = envelope
1815 .payload
1816 .position
1817 .and_then(deserialize_anchor)
1818 .ok_or_else(|| anyhow!("invalid position"))?;
1819 if !source_buffer.read(cx).can_resolve(&position) {
1820 return Err(anyhow!("cannot resolve position"));
1821 }
1822
1823 let definitions = self.definition(&source_buffer, position, cx);
1824 cx.spawn(|this, mut cx| async move {
1825 let definitions = definitions.await?;
1826 let mut response = proto::GetDefinitionResponse {
1827 definitions: Default::default(),
1828 };
1829 this.update(&mut cx, |this, cx| {
1830 for definition in definitions {
1831 let buffer =
1832 this.serialize_buffer_for_peer(&definition.target_buffer, sender_id, cx);
1833 response.definitions.push(proto::Definition {
1834 target_start: Some(serialize_anchor(&definition.target_range.start)),
1835 target_end: Some(serialize_anchor(&definition.target_range.end)),
1836 buffer: Some(buffer),
1837 });
1838 }
1839 });
1840 rpc.respond(receipt, response).await?;
1841 Ok::<_, anyhow::Error>(())
1842 })
1843 .detach_and_log_err(cx);
1844
1845 Ok(())
1846 }
1847
1848 pub fn handle_open_buffer(
1849 &mut self,
1850 envelope: TypedEnvelope<proto::OpenBuffer>,
1851 rpc: Arc<Client>,
1852 cx: &mut ModelContext<Self>,
1853 ) -> anyhow::Result<()> {
1854 let receipt = envelope.receipt();
1855 let peer_id = envelope.original_sender_id()?;
1856 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1857 let open_buffer = self.open_buffer(
1858 ProjectPath {
1859 worktree_id,
1860 path: PathBuf::from(envelope.payload.path).into(),
1861 },
1862 cx,
1863 );
1864 cx.spawn(|this, mut cx| {
1865 async move {
1866 let buffer = open_buffer.await?;
1867 let buffer = this.update(&mut cx, |this, cx| {
1868 this.serialize_buffer_for_peer(&buffer, peer_id, cx)
1869 });
1870 rpc.respond(
1871 receipt,
1872 proto::OpenBufferResponse {
1873 buffer: Some(buffer),
1874 },
1875 )
1876 .await
1877 }
1878 .log_err()
1879 })
1880 .detach();
1881 Ok(())
1882 }
1883
1884 fn serialize_buffer_for_peer(
1885 &mut self,
1886 buffer: &ModelHandle<Buffer>,
1887 peer_id: PeerId,
1888 cx: &AppContext,
1889 ) -> proto::Buffer {
1890 let buffer_id = buffer.read(cx).remote_id();
1891 let shared_buffers = self.shared_buffers.entry(peer_id).or_default();
1892 match shared_buffers.entry(buffer_id) {
1893 hash_map::Entry::Occupied(_) => proto::Buffer {
1894 variant: Some(proto::buffer::Variant::Id(buffer_id)),
1895 },
1896 hash_map::Entry::Vacant(entry) => {
1897 entry.insert(buffer.clone());
1898 proto::Buffer {
1899 variant: Some(proto::buffer::Variant::State(buffer.read(cx).to_proto())),
1900 }
1901 }
1902 }
1903 }
1904
1905 fn deserialize_remote_buffer(
1906 &mut self,
1907 buffer: proto::Buffer,
1908 cx: &mut ModelContext<Self>,
1909 ) -> Result<ModelHandle<Buffer>> {
1910 match buffer.variant.ok_or_else(|| anyhow!("missing buffer"))? {
1911 proto::buffer::Variant::Id(id) => self
1912 .open_buffers
1913 .get(&(id as usize))
1914 .and_then(|buffer| buffer.upgrade(cx))
1915 .ok_or_else(|| anyhow!("no buffer exists for id {}", id)),
1916 proto::buffer::Variant::State(mut buffer) => {
1917 let mut buffer_worktree = None;
1918 let mut buffer_file = None;
1919 if let Some(file) = buffer.file.take() {
1920 let worktree_id = WorktreeId::from_proto(file.worktree_id);
1921 let worktree = self
1922 .worktree_for_id(worktree_id, cx)
1923 .ok_or_else(|| anyhow!("no worktree found for id {}", file.worktree_id))?;
1924 buffer_file = Some(Box::new(File::from_proto(file, worktree.clone(), cx)?)
1925 as Box<dyn language::File>);
1926 buffer_worktree = Some(worktree);
1927 }
1928
1929 let buffer = cx.add_model(|cx| {
1930 Buffer::from_proto(self.replica_id(), buffer, buffer_file, cx).unwrap()
1931 });
1932 self.register_buffer(&buffer, buffer_worktree.as_ref(), cx)?;
1933 Ok(buffer)
1934 }
1935 }
1936 }
1937
1938 pub fn handle_close_buffer(
1939 &mut self,
1940 envelope: TypedEnvelope<proto::CloseBuffer>,
1941 _: Arc<Client>,
1942 cx: &mut ModelContext<Self>,
1943 ) -> anyhow::Result<()> {
1944 if let Some(shared_buffers) = self.shared_buffers.get_mut(&envelope.original_sender_id()?) {
1945 shared_buffers.remove(&envelope.payload.buffer_id);
1946 cx.notify();
1947 }
1948 Ok(())
1949 }
1950
1951 pub fn handle_buffer_saved(
1952 &mut self,
1953 envelope: TypedEnvelope<proto::BufferSaved>,
1954 _: Arc<Client>,
1955 cx: &mut ModelContext<Self>,
1956 ) -> Result<()> {
1957 let payload = envelope.payload.clone();
1958 let buffer = self
1959 .open_buffers
1960 .get(&(payload.buffer_id as usize))
1961 .and_then(|buffer| buffer.upgrade(cx));
1962 if let Some(buffer) = buffer {
1963 buffer.update(cx, |buffer, cx| {
1964 let version = payload.version.try_into()?;
1965 let mtime = payload
1966 .mtime
1967 .ok_or_else(|| anyhow!("missing mtime"))?
1968 .into();
1969 buffer.did_save(version, mtime, None, cx);
1970 Result::<_, anyhow::Error>::Ok(())
1971 })?;
1972 }
1973 Ok(())
1974 }
1975
1976 pub fn handle_buffer_reloaded(
1977 &mut self,
1978 envelope: TypedEnvelope<proto::BufferReloaded>,
1979 _: Arc<Client>,
1980 cx: &mut ModelContext<Self>,
1981 ) -> Result<()> {
1982 let payload = envelope.payload.clone();
1983 let buffer = self
1984 .open_buffers
1985 .get(&(payload.buffer_id as usize))
1986 .and_then(|buffer| buffer.upgrade(cx));
1987 if let Some(buffer) = buffer {
1988 buffer.update(cx, |buffer, cx| {
1989 let version = payload.version.try_into()?;
1990 let mtime = payload
1991 .mtime
1992 .ok_or_else(|| anyhow!("missing mtime"))?
1993 .into();
1994 buffer.did_reload(version, mtime, cx);
1995 Result::<_, anyhow::Error>::Ok(())
1996 })?;
1997 }
1998 Ok(())
1999 }
2000
2001 pub fn match_paths<'a>(
2002 &self,
2003 query: &'a str,
2004 include_ignored: bool,
2005 smart_case: bool,
2006 max_results: usize,
2007 cancel_flag: &'a AtomicBool,
2008 cx: &AppContext,
2009 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
2010 let worktrees = self
2011 .worktrees(cx)
2012 .filter(|worktree| !worktree.read(cx).is_weak())
2013 .collect::<Vec<_>>();
2014 let include_root_name = worktrees.len() > 1;
2015 let candidate_sets = worktrees
2016 .into_iter()
2017 .map(|worktree| CandidateSet {
2018 snapshot: worktree.read(cx).snapshot(),
2019 include_ignored,
2020 include_root_name,
2021 })
2022 .collect::<Vec<_>>();
2023
2024 let background = cx.background().clone();
2025 async move {
2026 fuzzy::match_paths(
2027 candidate_sets.as_slice(),
2028 query,
2029 smart_case,
2030 max_results,
2031 cancel_flag,
2032 background,
2033 )
2034 .await
2035 }
2036 }
2037}
2038
2039impl WorktreeHandle {
2040 pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
2041 match self {
2042 WorktreeHandle::Strong(handle) => Some(handle.clone()),
2043 WorktreeHandle::Weak(handle) => handle.upgrade(cx),
2044 }
2045 }
2046}
2047
2048struct CandidateSet {
2049 snapshot: Snapshot,
2050 include_ignored: bool,
2051 include_root_name: bool,
2052}
2053
2054impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
2055 type Candidates = CandidateSetIter<'a>;
2056
2057 fn id(&self) -> usize {
2058 self.snapshot.id().to_usize()
2059 }
2060
2061 fn len(&self) -> usize {
2062 if self.include_ignored {
2063 self.snapshot.file_count()
2064 } else {
2065 self.snapshot.visible_file_count()
2066 }
2067 }
2068
2069 fn prefix(&self) -> Arc<str> {
2070 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
2071 self.snapshot.root_name().into()
2072 } else if self.include_root_name {
2073 format!("{}/", self.snapshot.root_name()).into()
2074 } else {
2075 "".into()
2076 }
2077 }
2078
2079 fn candidates(&'a self, start: usize) -> Self::Candidates {
2080 CandidateSetIter {
2081 traversal: self.snapshot.files(self.include_ignored, start),
2082 }
2083 }
2084}
2085
2086struct CandidateSetIter<'a> {
2087 traversal: Traversal<'a>,
2088}
2089
2090impl<'a> Iterator for CandidateSetIter<'a> {
2091 type Item = PathMatchCandidate<'a>;
2092
2093 fn next(&mut self) -> Option<Self::Item> {
2094 self.traversal.next().map(|entry| {
2095 if let EntryKind::File(char_bag) = entry.kind {
2096 PathMatchCandidate {
2097 path: &entry.path,
2098 char_bag,
2099 }
2100 } else {
2101 unreachable!()
2102 }
2103 })
2104 }
2105}
2106
2107impl Entity for Project {
2108 type Event = Event;
2109
2110 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
2111 match &self.client_state {
2112 ProjectClientState::Local { remote_id_rx, .. } => {
2113 if let Some(project_id) = *remote_id_rx.borrow() {
2114 let rpc = self.client.clone();
2115 cx.spawn(|_| async move {
2116 if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
2117 log::error!("error unregistering project: {}", err);
2118 }
2119 })
2120 .detach();
2121 }
2122 }
2123 ProjectClientState::Remote { remote_id, .. } => {
2124 let rpc = self.client.clone();
2125 let project_id = *remote_id;
2126 cx.spawn(|_| async move {
2127 if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
2128 log::error!("error leaving project: {}", err);
2129 }
2130 })
2131 .detach();
2132 }
2133 }
2134 }
2135
2136 fn app_will_quit(
2137 &mut self,
2138 _: &mut MutableAppContext,
2139 ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
2140 use futures::FutureExt;
2141
2142 let shutdown_futures = self
2143 .language_servers
2144 .drain()
2145 .filter_map(|(_, server)| server.shutdown())
2146 .collect::<Vec<_>>();
2147 Some(
2148 async move {
2149 futures::future::join_all(shutdown_futures).await;
2150 }
2151 .boxed(),
2152 )
2153 }
2154}
2155
2156impl Collaborator {
2157 fn from_proto(
2158 message: proto::Collaborator,
2159 user_store: &ModelHandle<UserStore>,
2160 cx: &mut AsyncAppContext,
2161 ) -> impl Future<Output = Result<Self>> {
2162 let user = user_store.update(cx, |user_store, cx| {
2163 user_store.fetch_user(message.user_id, cx)
2164 });
2165
2166 async move {
2167 Ok(Self {
2168 peer_id: PeerId(message.peer_id),
2169 user: user.await?,
2170 replica_id: message.replica_id as ReplicaId,
2171 })
2172 }
2173 }
2174}
2175
2176impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
2177 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
2178 Self {
2179 worktree_id,
2180 path: path.as_ref().into(),
2181 }
2182 }
2183}
2184
2185#[cfg(test)]
2186mod tests {
2187 use super::{Event, *};
2188 use client::test::FakeHttpClient;
2189 use fs::RealFs;
2190 use futures::StreamExt;
2191 use gpui::{test::subscribe, TestAppContext};
2192 use language::{
2193 tree_sitter_rust, AnchorRangeExt, Diagnostic, LanguageConfig, LanguageRegistry,
2194 LanguageServerConfig, Point,
2195 };
2196 use lsp::Url;
2197 use serde_json::json;
2198 use std::{cell::RefCell, os::unix, path::PathBuf, rc::Rc};
2199 use unindent::Unindent as _;
2200 use util::test::temp_tree;
2201 use worktree::WorktreeHandle as _;
2202
2203 #[gpui::test]
2204 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
2205 let dir = temp_tree(json!({
2206 "root": {
2207 "apple": "",
2208 "banana": {
2209 "carrot": {
2210 "date": "",
2211 "endive": "",
2212 }
2213 },
2214 "fennel": {
2215 "grape": "",
2216 }
2217 }
2218 }));
2219
2220 let root_link_path = dir.path().join("root_link");
2221 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
2222 unix::fs::symlink(
2223 &dir.path().join("root/fennel"),
2224 &dir.path().join("root/finnochio"),
2225 )
2226 .unwrap();
2227
2228 let project = build_project(Arc::new(RealFs), &mut cx);
2229
2230 let (tree, _) = project
2231 .update(&mut cx, |project, cx| {
2232 project.find_or_create_local_worktree(&root_link_path, false, cx)
2233 })
2234 .await
2235 .unwrap();
2236
2237 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2238 .await;
2239 cx.read(|cx| {
2240 let tree = tree.read(cx);
2241 assert_eq!(tree.file_count(), 5);
2242 assert_eq!(
2243 tree.inode_for_path("fennel/grape"),
2244 tree.inode_for_path("finnochio/grape")
2245 );
2246 });
2247
2248 let cancel_flag = Default::default();
2249 let results = project
2250 .read_with(&cx, |project, cx| {
2251 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
2252 })
2253 .await;
2254 assert_eq!(
2255 results
2256 .into_iter()
2257 .map(|result| result.path)
2258 .collect::<Vec<Arc<Path>>>(),
2259 vec![
2260 PathBuf::from("banana/carrot/date").into(),
2261 PathBuf::from("banana/carrot/endive").into(),
2262 ]
2263 );
2264 }
2265
2266 #[gpui::test]
2267 async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
2268 let (language_server_config, mut fake_server) =
2269 LanguageServerConfig::fake(cx.background()).await;
2270 let progress_token = language_server_config
2271 .disk_based_diagnostics_progress_token
2272 .clone()
2273 .unwrap();
2274
2275 let mut languages = LanguageRegistry::new();
2276 languages.add(Arc::new(Language::new(
2277 LanguageConfig {
2278 name: "Rust".to_string(),
2279 path_suffixes: vec!["rs".to_string()],
2280 language_server: Some(language_server_config),
2281 ..Default::default()
2282 },
2283 Some(tree_sitter_rust::language()),
2284 )));
2285
2286 let dir = temp_tree(json!({
2287 "a.rs": "fn a() { A }",
2288 "b.rs": "const y: i32 = 1",
2289 }));
2290
2291 let http_client = FakeHttpClient::with_404_response();
2292 let client = Client::new(http_client.clone());
2293 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
2294
2295 let project = cx.update(|cx| {
2296 Project::local(
2297 client,
2298 user_store,
2299 Arc::new(languages),
2300 Arc::new(RealFs),
2301 cx,
2302 )
2303 });
2304
2305 let (tree, _) = project
2306 .update(&mut cx, |project, cx| {
2307 project.find_or_create_local_worktree(dir.path(), false, cx)
2308 })
2309 .await
2310 .unwrap();
2311 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
2312
2313 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2314 .await;
2315
2316 // Cause worktree to start the fake language server
2317 let _buffer = project
2318 .update(&mut cx, |project, cx| {
2319 project.open_buffer(
2320 ProjectPath {
2321 worktree_id,
2322 path: Path::new("b.rs").into(),
2323 },
2324 cx,
2325 )
2326 })
2327 .await
2328 .unwrap();
2329
2330 let mut events = subscribe(&project, &mut cx);
2331
2332 fake_server.start_progress(&progress_token).await;
2333 assert_eq!(
2334 events.next().await.unwrap(),
2335 Event::DiskBasedDiagnosticsStarted
2336 );
2337
2338 fake_server.start_progress(&progress_token).await;
2339 fake_server.end_progress(&progress_token).await;
2340 fake_server.start_progress(&progress_token).await;
2341
2342 fake_server
2343 .notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
2344 uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
2345 version: None,
2346 diagnostics: vec![lsp::Diagnostic {
2347 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
2348 severity: Some(lsp::DiagnosticSeverity::ERROR),
2349 message: "undefined variable 'A'".to_string(),
2350 ..Default::default()
2351 }],
2352 })
2353 .await;
2354 assert_eq!(
2355 events.next().await.unwrap(),
2356 Event::DiagnosticsUpdated(ProjectPath {
2357 worktree_id,
2358 path: Arc::from(Path::new("a.rs"))
2359 })
2360 );
2361
2362 fake_server.end_progress(&progress_token).await;
2363 fake_server.end_progress(&progress_token).await;
2364 assert_eq!(
2365 events.next().await.unwrap(),
2366 Event::DiskBasedDiagnosticsUpdated
2367 );
2368 assert_eq!(
2369 events.next().await.unwrap(),
2370 Event::DiskBasedDiagnosticsFinished
2371 );
2372
2373 let buffer = project
2374 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
2375 .await
2376 .unwrap();
2377
2378 buffer.read_with(&cx, |buffer, _| {
2379 let snapshot = buffer.snapshot();
2380 let diagnostics = snapshot
2381 .diagnostics_in_range::<_, Point>(0..buffer.len())
2382 .collect::<Vec<_>>();
2383 assert_eq!(
2384 diagnostics,
2385 &[DiagnosticEntry {
2386 range: Point::new(0, 9)..Point::new(0, 10),
2387 diagnostic: Diagnostic {
2388 severity: lsp::DiagnosticSeverity::ERROR,
2389 message: "undefined variable 'A'".to_string(),
2390 group_id: 0,
2391 is_primary: true,
2392 ..Default::default()
2393 }
2394 }]
2395 )
2396 });
2397 }
2398
2399 #[gpui::test]
2400 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
2401 let dir = temp_tree(json!({
2402 "root": {
2403 "dir1": {},
2404 "dir2": {
2405 "dir3": {}
2406 }
2407 }
2408 }));
2409
2410 let project = build_project(Arc::new(RealFs), &mut cx);
2411 let (tree, _) = project
2412 .update(&mut cx, |project, cx| {
2413 project.find_or_create_local_worktree(&dir.path(), false, cx)
2414 })
2415 .await
2416 .unwrap();
2417
2418 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2419 .await;
2420
2421 let cancel_flag = Default::default();
2422 let results = project
2423 .read_with(&cx, |project, cx| {
2424 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
2425 })
2426 .await;
2427
2428 assert!(results.is_empty());
2429 }
2430
2431 #[gpui::test]
2432 async fn test_definition(mut cx: gpui::TestAppContext) {
2433 let (language_server_config, mut fake_server) =
2434 LanguageServerConfig::fake(cx.background()).await;
2435
2436 let mut languages = LanguageRegistry::new();
2437 languages.add(Arc::new(Language::new(
2438 LanguageConfig {
2439 name: "Rust".to_string(),
2440 path_suffixes: vec!["rs".to_string()],
2441 language_server: Some(language_server_config),
2442 ..Default::default()
2443 },
2444 Some(tree_sitter_rust::language()),
2445 )));
2446
2447 let dir = temp_tree(json!({
2448 "a.rs": "const fn a() { A }",
2449 "b.rs": "const y: i32 = crate::a()",
2450 }));
2451
2452 let http_client = FakeHttpClient::with_404_response();
2453 let client = Client::new(http_client.clone());
2454 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
2455 let project = cx.update(|cx| {
2456 Project::local(
2457 client,
2458 user_store,
2459 Arc::new(languages),
2460 Arc::new(RealFs),
2461 cx,
2462 )
2463 });
2464
2465 let (tree, _) = project
2466 .update(&mut cx, |project, cx| {
2467 project.find_or_create_local_worktree(dir.path().join("b.rs"), false, cx)
2468 })
2469 .await
2470 .unwrap();
2471 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
2472 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2473 .await;
2474
2475 // Cause worktree to start the fake language server
2476 let buffer = project
2477 .update(&mut cx, |project, cx| {
2478 project.open_buffer(
2479 ProjectPath {
2480 worktree_id,
2481 path: Path::new("").into(),
2482 },
2483 cx,
2484 )
2485 })
2486 .await
2487 .unwrap();
2488 let definitions =
2489 project.update(&mut cx, |project, cx| project.definition(&buffer, 22, cx));
2490 let (request_id, request) = fake_server
2491 .receive_request::<lsp::request::GotoDefinition>()
2492 .await;
2493 let request_params = request.text_document_position_params;
2494 assert_eq!(
2495 request_params.text_document.uri.to_file_path().unwrap(),
2496 dir.path().join("b.rs")
2497 );
2498 assert_eq!(request_params.position, lsp::Position::new(0, 22));
2499
2500 fake_server
2501 .respond(
2502 request_id,
2503 Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
2504 lsp::Url::from_file_path(dir.path().join("a.rs")).unwrap(),
2505 lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
2506 ))),
2507 )
2508 .await;
2509 let mut definitions = definitions.await.unwrap();
2510 assert_eq!(definitions.len(), 1);
2511 let definition = definitions.pop().unwrap();
2512 cx.update(|cx| {
2513 let target_buffer = definition.target_buffer.read(cx);
2514 assert_eq!(
2515 target_buffer
2516 .file()
2517 .unwrap()
2518 .as_local()
2519 .unwrap()
2520 .abs_path(cx),
2521 dir.path().join("a.rs")
2522 );
2523 assert_eq!(definition.target_range.to_offset(target_buffer), 9..10);
2524 assert_eq!(
2525 list_worktrees(&project, cx),
2526 [
2527 (dir.path().join("b.rs"), false),
2528 (dir.path().join("a.rs"), true)
2529 ]
2530 );
2531
2532 drop(definition);
2533 });
2534 cx.read(|cx| {
2535 assert_eq!(
2536 list_worktrees(&project, cx),
2537 [(dir.path().join("b.rs"), false)]
2538 );
2539 });
2540
2541 fn list_worktrees(project: &ModelHandle<Project>, cx: &AppContext) -> Vec<(PathBuf, bool)> {
2542 project
2543 .read(cx)
2544 .worktrees(cx)
2545 .map(|worktree| {
2546 let worktree = worktree.read(cx);
2547 (
2548 worktree.as_local().unwrap().abs_path().to_path_buf(),
2549 worktree.is_weak(),
2550 )
2551 })
2552 .collect::<Vec<_>>()
2553 }
2554 }
2555
2556 #[gpui::test]
2557 async fn test_save_file(mut cx: gpui::TestAppContext) {
2558 let fs = Arc::new(FakeFs::new(cx.background()));
2559 fs.insert_tree(
2560 "/dir",
2561 json!({
2562 "file1": "the old contents",
2563 }),
2564 )
2565 .await;
2566
2567 let project = build_project(fs.clone(), &mut cx);
2568 let worktree_id = project
2569 .update(&mut cx, |p, cx| {
2570 p.find_or_create_local_worktree("/dir", false, cx)
2571 })
2572 .await
2573 .unwrap()
2574 .0
2575 .read_with(&cx, |tree, _| tree.id());
2576
2577 let buffer = project
2578 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
2579 .await
2580 .unwrap();
2581 buffer
2582 .update(&mut cx, |buffer, cx| {
2583 assert_eq!(buffer.text(), "the old contents");
2584 buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
2585 buffer.save(cx)
2586 })
2587 .await
2588 .unwrap();
2589
2590 let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2591 assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
2592 }
2593
2594 #[gpui::test]
2595 async fn test_save_in_single_file_worktree(mut cx: gpui::TestAppContext) {
2596 let fs = Arc::new(FakeFs::new(cx.background()));
2597 fs.insert_tree(
2598 "/dir",
2599 json!({
2600 "file1": "the old contents",
2601 }),
2602 )
2603 .await;
2604
2605 let project = build_project(fs.clone(), &mut cx);
2606 let worktree_id = project
2607 .update(&mut cx, |p, cx| {
2608 p.find_or_create_local_worktree("/dir/file1", false, cx)
2609 })
2610 .await
2611 .unwrap()
2612 .0
2613 .read_with(&cx, |tree, _| tree.id());
2614
2615 let buffer = project
2616 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, ""), cx))
2617 .await
2618 .unwrap();
2619 buffer
2620 .update(&mut cx, |buffer, cx| {
2621 buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
2622 buffer.save(cx)
2623 })
2624 .await
2625 .unwrap();
2626
2627 let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2628 assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
2629 }
2630
2631 #[gpui::test(retries = 5)]
2632 async fn test_rescan_and_remote_updates(mut cx: gpui::TestAppContext) {
2633 let dir = temp_tree(json!({
2634 "a": {
2635 "file1": "",
2636 "file2": "",
2637 "file3": "",
2638 },
2639 "b": {
2640 "c": {
2641 "file4": "",
2642 "file5": "",
2643 }
2644 }
2645 }));
2646
2647 let project = build_project(Arc::new(RealFs), &mut cx);
2648 let rpc = project.read_with(&cx, |p, _| p.client.clone());
2649
2650 let (tree, _) = project
2651 .update(&mut cx, |p, cx| {
2652 p.find_or_create_local_worktree(dir.path(), false, cx)
2653 })
2654 .await
2655 .unwrap();
2656 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
2657
2658 let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
2659 let buffer = project.update(cx, |p, cx| p.open_buffer((worktree_id, path), cx));
2660 async move { buffer.await.unwrap() }
2661 };
2662 let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
2663 tree.read_with(cx, |tree, _| {
2664 tree.entry_for_path(path)
2665 .expect(&format!("no entry for path {}", path))
2666 .id
2667 })
2668 };
2669
2670 let buffer2 = buffer_for_path("a/file2", &mut cx).await;
2671 let buffer3 = buffer_for_path("a/file3", &mut cx).await;
2672 let buffer4 = buffer_for_path("b/c/file4", &mut cx).await;
2673 let buffer5 = buffer_for_path("b/c/file5", &mut cx).await;
2674
2675 let file2_id = id_for_path("a/file2", &cx);
2676 let file3_id = id_for_path("a/file3", &cx);
2677 let file4_id = id_for_path("b/c/file4", &cx);
2678
2679 // Wait for the initial scan.
2680 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2681 .await;
2682
2683 // Create a remote copy of this worktree.
2684 let initial_snapshot = tree.read_with(&cx, |tree, _| tree.snapshot());
2685 let (remote, load_task) = cx.update(|cx| {
2686 Worktree::remote(
2687 1,
2688 1,
2689 initial_snapshot.to_proto(&Default::default(), Default::default()),
2690 rpc.clone(),
2691 cx,
2692 )
2693 });
2694 load_task.await;
2695
2696 cx.read(|cx| {
2697 assert!(!buffer2.read(cx).is_dirty());
2698 assert!(!buffer3.read(cx).is_dirty());
2699 assert!(!buffer4.read(cx).is_dirty());
2700 assert!(!buffer5.read(cx).is_dirty());
2701 });
2702
2703 // Rename and delete files and directories.
2704 tree.flush_fs_events(&cx).await;
2705 std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
2706 std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
2707 std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
2708 std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
2709 tree.flush_fs_events(&cx).await;
2710
2711 let expected_paths = vec![
2712 "a",
2713 "a/file1",
2714 "a/file2.new",
2715 "b",
2716 "d",
2717 "d/file3",
2718 "d/file4",
2719 ];
2720
2721 cx.read(|app| {
2722 assert_eq!(
2723 tree.read(app)
2724 .paths()
2725 .map(|p| p.to_str().unwrap())
2726 .collect::<Vec<_>>(),
2727 expected_paths
2728 );
2729
2730 assert_eq!(id_for_path("a/file2.new", &cx), file2_id);
2731 assert_eq!(id_for_path("d/file3", &cx), file3_id);
2732 assert_eq!(id_for_path("d/file4", &cx), file4_id);
2733
2734 assert_eq!(
2735 buffer2.read(app).file().unwrap().path().as_ref(),
2736 Path::new("a/file2.new")
2737 );
2738 assert_eq!(
2739 buffer3.read(app).file().unwrap().path().as_ref(),
2740 Path::new("d/file3")
2741 );
2742 assert_eq!(
2743 buffer4.read(app).file().unwrap().path().as_ref(),
2744 Path::new("d/file4")
2745 );
2746 assert_eq!(
2747 buffer5.read(app).file().unwrap().path().as_ref(),
2748 Path::new("b/c/file5")
2749 );
2750
2751 assert!(!buffer2.read(app).file().unwrap().is_deleted());
2752 assert!(!buffer3.read(app).file().unwrap().is_deleted());
2753 assert!(!buffer4.read(app).file().unwrap().is_deleted());
2754 assert!(buffer5.read(app).file().unwrap().is_deleted());
2755 });
2756
2757 // Update the remote worktree. Check that it becomes consistent with the
2758 // local worktree.
2759 remote.update(&mut cx, |remote, cx| {
2760 let update_message =
2761 tree.read(cx)
2762 .snapshot()
2763 .build_update(&initial_snapshot, 1, 1, true);
2764 remote
2765 .as_remote_mut()
2766 .unwrap()
2767 .snapshot
2768 .apply_remote_update(update_message)
2769 .unwrap();
2770
2771 assert_eq!(
2772 remote
2773 .paths()
2774 .map(|p| p.to_str().unwrap())
2775 .collect::<Vec<_>>(),
2776 expected_paths
2777 );
2778 });
2779 }
2780
2781 #[gpui::test]
2782 async fn test_buffer_deduping(mut cx: gpui::TestAppContext) {
2783 let fs = Arc::new(FakeFs::new(cx.background()));
2784 fs.insert_tree(
2785 "/the-dir",
2786 json!({
2787 "a.txt": "a-contents",
2788 "b.txt": "b-contents",
2789 }),
2790 )
2791 .await;
2792
2793 let project = build_project(fs.clone(), &mut cx);
2794 let worktree_id = project
2795 .update(&mut cx, |p, cx| {
2796 p.find_or_create_local_worktree("/the-dir", false, cx)
2797 })
2798 .await
2799 .unwrap()
2800 .0
2801 .read_with(&cx, |tree, _| tree.id());
2802
2803 // Spawn multiple tasks to open paths, repeating some paths.
2804 let (buffer_a_1, buffer_b, buffer_a_2) = project.update(&mut cx, |p, cx| {
2805 (
2806 p.open_buffer((worktree_id, "a.txt"), cx),
2807 p.open_buffer((worktree_id, "b.txt"), cx),
2808 p.open_buffer((worktree_id, "a.txt"), cx),
2809 )
2810 });
2811
2812 let buffer_a_1 = buffer_a_1.await.unwrap();
2813 let buffer_a_2 = buffer_a_2.await.unwrap();
2814 let buffer_b = buffer_b.await.unwrap();
2815 assert_eq!(buffer_a_1.read_with(&cx, |b, _| b.text()), "a-contents");
2816 assert_eq!(buffer_b.read_with(&cx, |b, _| b.text()), "b-contents");
2817
2818 // There is only one buffer per path.
2819 let buffer_a_id = buffer_a_1.id();
2820 assert_eq!(buffer_a_2.id(), buffer_a_id);
2821
2822 // Open the same path again while it is still open.
2823 drop(buffer_a_1);
2824 let buffer_a_3 = project
2825 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2826 .await
2827 .unwrap();
2828
2829 // There's still only one buffer per path.
2830 assert_eq!(buffer_a_3.id(), buffer_a_id);
2831 }
2832
2833 #[gpui::test]
2834 async fn test_buffer_is_dirty(mut cx: gpui::TestAppContext) {
2835 use std::fs;
2836
2837 let dir = temp_tree(json!({
2838 "file1": "abc",
2839 "file2": "def",
2840 "file3": "ghi",
2841 }));
2842
2843 let project = build_project(Arc::new(RealFs), &mut cx);
2844 let (worktree, _) = project
2845 .update(&mut cx, |p, cx| {
2846 p.find_or_create_local_worktree(dir.path(), false, cx)
2847 })
2848 .await
2849 .unwrap();
2850 let worktree_id = worktree.read_with(&cx, |worktree, _| worktree.id());
2851
2852 worktree.flush_fs_events(&cx).await;
2853 worktree
2854 .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete())
2855 .await;
2856
2857 let buffer1 = project
2858 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
2859 .await
2860 .unwrap();
2861 let events = Rc::new(RefCell::new(Vec::new()));
2862
2863 // initially, the buffer isn't dirty.
2864 buffer1.update(&mut cx, |buffer, cx| {
2865 cx.subscribe(&buffer1, {
2866 let events = events.clone();
2867 move |_, _, event, _| events.borrow_mut().push(event.clone())
2868 })
2869 .detach();
2870
2871 assert!(!buffer.is_dirty());
2872 assert!(events.borrow().is_empty());
2873
2874 buffer.edit(vec![1..2], "", cx);
2875 });
2876
2877 // after the first edit, the buffer is dirty, and emits a dirtied event.
2878 buffer1.update(&mut cx, |buffer, cx| {
2879 assert!(buffer.text() == "ac");
2880 assert!(buffer.is_dirty());
2881 assert_eq!(
2882 *events.borrow(),
2883 &[language::Event::Edited, language::Event::Dirtied]
2884 );
2885 events.borrow_mut().clear();
2886 buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
2887 });
2888
2889 // after saving, the buffer is not dirty, and emits a saved event.
2890 buffer1.update(&mut cx, |buffer, cx| {
2891 assert!(!buffer.is_dirty());
2892 assert_eq!(*events.borrow(), &[language::Event::Saved]);
2893 events.borrow_mut().clear();
2894
2895 buffer.edit(vec![1..1], "B", cx);
2896 buffer.edit(vec![2..2], "D", cx);
2897 });
2898
2899 // after editing again, the buffer is dirty, and emits another dirty event.
2900 buffer1.update(&mut cx, |buffer, cx| {
2901 assert!(buffer.text() == "aBDc");
2902 assert!(buffer.is_dirty());
2903 assert_eq!(
2904 *events.borrow(),
2905 &[
2906 language::Event::Edited,
2907 language::Event::Dirtied,
2908 language::Event::Edited,
2909 ],
2910 );
2911 events.borrow_mut().clear();
2912
2913 // TODO - currently, after restoring the buffer to its
2914 // previously-saved state, the is still considered dirty.
2915 buffer.edit([1..3], "", cx);
2916 assert!(buffer.text() == "ac");
2917 assert!(buffer.is_dirty());
2918 });
2919
2920 assert_eq!(*events.borrow(), &[language::Event::Edited]);
2921
2922 // When a file is deleted, the buffer is considered dirty.
2923 let events = Rc::new(RefCell::new(Vec::new()));
2924 let buffer2 = project
2925 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file2"), cx))
2926 .await
2927 .unwrap();
2928 buffer2.update(&mut cx, |_, cx| {
2929 cx.subscribe(&buffer2, {
2930 let events = events.clone();
2931 move |_, _, event, _| events.borrow_mut().push(event.clone())
2932 })
2933 .detach();
2934 });
2935
2936 fs::remove_file(dir.path().join("file2")).unwrap();
2937 buffer2.condition(&cx, |b, _| b.is_dirty()).await;
2938 assert_eq!(
2939 *events.borrow(),
2940 &[language::Event::Dirtied, language::Event::FileHandleChanged]
2941 );
2942
2943 // When a file is already dirty when deleted, we don't emit a Dirtied event.
2944 let events = Rc::new(RefCell::new(Vec::new()));
2945 let buffer3 = project
2946 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file3"), cx))
2947 .await
2948 .unwrap();
2949 buffer3.update(&mut cx, |_, cx| {
2950 cx.subscribe(&buffer3, {
2951 let events = events.clone();
2952 move |_, _, event, _| events.borrow_mut().push(event.clone())
2953 })
2954 .detach();
2955 });
2956
2957 worktree.flush_fs_events(&cx).await;
2958 buffer3.update(&mut cx, |buffer, cx| {
2959 buffer.edit(Some(0..0), "x", cx);
2960 });
2961 events.borrow_mut().clear();
2962 fs::remove_file(dir.path().join("file3")).unwrap();
2963 buffer3
2964 .condition(&cx, |_, _| !events.borrow().is_empty())
2965 .await;
2966 assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]);
2967 cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
2968 }
2969
2970 #[gpui::test]
2971 async fn test_buffer_file_changes_on_disk(mut cx: gpui::TestAppContext) {
2972 use std::fs;
2973
2974 let initial_contents = "aaa\nbbbbb\nc\n";
2975 let dir = temp_tree(json!({ "the-file": initial_contents }));
2976
2977 let project = build_project(Arc::new(RealFs), &mut cx);
2978 let (worktree, _) = project
2979 .update(&mut cx, |p, cx| {
2980 p.find_or_create_local_worktree(dir.path(), false, cx)
2981 })
2982 .await
2983 .unwrap();
2984 let worktree_id = worktree.read_with(&cx, |tree, _| tree.id());
2985
2986 worktree
2987 .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete())
2988 .await;
2989
2990 let abs_path = dir.path().join("the-file");
2991 let buffer = project
2992 .update(&mut cx, |p, cx| {
2993 p.open_buffer((worktree_id, "the-file"), cx)
2994 })
2995 .await
2996 .unwrap();
2997
2998 // TODO
2999 // Add a cursor on each row.
3000 // let selection_set_id = buffer.update(&mut cx, |buffer, cx| {
3001 // assert!(!buffer.is_dirty());
3002 // buffer.add_selection_set(
3003 // &(0..3)
3004 // .map(|row| Selection {
3005 // id: row as usize,
3006 // start: Point::new(row, 1),
3007 // end: Point::new(row, 1),
3008 // reversed: false,
3009 // goal: SelectionGoal::None,
3010 // })
3011 // .collect::<Vec<_>>(),
3012 // cx,
3013 // )
3014 // });
3015
3016 // Change the file on disk, adding two new lines of text, and removing
3017 // one line.
3018 buffer.read_with(&cx, |buffer, _| {
3019 assert!(!buffer.is_dirty());
3020 assert!(!buffer.has_conflict());
3021 });
3022 let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
3023 fs::write(&abs_path, new_contents).unwrap();
3024
3025 // Because the buffer was not modified, it is reloaded from disk. Its
3026 // contents are edited according to the diff between the old and new
3027 // file contents.
3028 buffer
3029 .condition(&cx, |buffer, _| buffer.text() == new_contents)
3030 .await;
3031
3032 buffer.update(&mut cx, |buffer, _| {
3033 assert_eq!(buffer.text(), new_contents);
3034 assert!(!buffer.is_dirty());
3035 assert!(!buffer.has_conflict());
3036
3037 // TODO
3038 // let cursor_positions = buffer
3039 // .selection_set(selection_set_id)
3040 // .unwrap()
3041 // .selections::<Point>(&*buffer)
3042 // .map(|selection| {
3043 // assert_eq!(selection.start, selection.end);
3044 // selection.start
3045 // })
3046 // .collect::<Vec<_>>();
3047 // assert_eq!(
3048 // cursor_positions,
3049 // [Point::new(1, 1), Point::new(3, 1), Point::new(4, 0)]
3050 // );
3051 });
3052
3053 // Modify the buffer
3054 buffer.update(&mut cx, |buffer, cx| {
3055 buffer.edit(vec![0..0], " ", cx);
3056 assert!(buffer.is_dirty());
3057 assert!(!buffer.has_conflict());
3058 });
3059
3060 // Change the file on disk again, adding blank lines to the beginning.
3061 fs::write(&abs_path, "\n\n\nAAAA\naaa\nBB\nbbbbb\n").unwrap();
3062
3063 // Because the buffer is modified, it doesn't reload from disk, but is
3064 // marked as having a conflict.
3065 buffer
3066 .condition(&cx, |buffer, _| buffer.has_conflict())
3067 .await;
3068 }
3069
3070 #[gpui::test]
3071 async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
3072 let fs = Arc::new(FakeFs::new(cx.background()));
3073 fs.insert_tree(
3074 "/the-dir",
3075 json!({
3076 "a.rs": "
3077 fn foo(mut v: Vec<usize>) {
3078 for x in &v {
3079 v.push(1);
3080 }
3081 }
3082 "
3083 .unindent(),
3084 }),
3085 )
3086 .await;
3087
3088 let project = build_project(fs.clone(), &mut cx);
3089 let (worktree, _) = project
3090 .update(&mut cx, |p, cx| {
3091 p.find_or_create_local_worktree("/the-dir", false, cx)
3092 })
3093 .await
3094 .unwrap();
3095 let worktree_id = worktree.read_with(&cx, |tree, _| tree.id());
3096
3097 let buffer = project
3098 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
3099 .await
3100 .unwrap();
3101
3102 let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
3103 let message = lsp::PublishDiagnosticsParams {
3104 uri: buffer_uri.clone(),
3105 diagnostics: vec![
3106 lsp::Diagnostic {
3107 range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
3108 severity: Some(DiagnosticSeverity::WARNING),
3109 message: "error 1".to_string(),
3110 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3111 location: lsp::Location {
3112 uri: buffer_uri.clone(),
3113 range: lsp::Range::new(
3114 lsp::Position::new(1, 8),
3115 lsp::Position::new(1, 9),
3116 ),
3117 },
3118 message: "error 1 hint 1".to_string(),
3119 }]),
3120 ..Default::default()
3121 },
3122 lsp::Diagnostic {
3123 range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
3124 severity: Some(DiagnosticSeverity::HINT),
3125 message: "error 1 hint 1".to_string(),
3126 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3127 location: lsp::Location {
3128 uri: buffer_uri.clone(),
3129 range: lsp::Range::new(
3130 lsp::Position::new(1, 8),
3131 lsp::Position::new(1, 9),
3132 ),
3133 },
3134 message: "original diagnostic".to_string(),
3135 }]),
3136 ..Default::default()
3137 },
3138 lsp::Diagnostic {
3139 range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
3140 severity: Some(DiagnosticSeverity::ERROR),
3141 message: "error 2".to_string(),
3142 related_information: Some(vec![
3143 lsp::DiagnosticRelatedInformation {
3144 location: lsp::Location {
3145 uri: buffer_uri.clone(),
3146 range: lsp::Range::new(
3147 lsp::Position::new(1, 13),
3148 lsp::Position::new(1, 15),
3149 ),
3150 },
3151 message: "error 2 hint 1".to_string(),
3152 },
3153 lsp::DiagnosticRelatedInformation {
3154 location: lsp::Location {
3155 uri: buffer_uri.clone(),
3156 range: lsp::Range::new(
3157 lsp::Position::new(1, 13),
3158 lsp::Position::new(1, 15),
3159 ),
3160 },
3161 message: "error 2 hint 2".to_string(),
3162 },
3163 ]),
3164 ..Default::default()
3165 },
3166 lsp::Diagnostic {
3167 range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
3168 severity: Some(DiagnosticSeverity::HINT),
3169 message: "error 2 hint 1".to_string(),
3170 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3171 location: lsp::Location {
3172 uri: buffer_uri.clone(),
3173 range: lsp::Range::new(
3174 lsp::Position::new(2, 8),
3175 lsp::Position::new(2, 17),
3176 ),
3177 },
3178 message: "original diagnostic".to_string(),
3179 }]),
3180 ..Default::default()
3181 },
3182 lsp::Diagnostic {
3183 range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
3184 severity: Some(DiagnosticSeverity::HINT),
3185 message: "error 2 hint 2".to_string(),
3186 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3187 location: lsp::Location {
3188 uri: buffer_uri.clone(),
3189 range: lsp::Range::new(
3190 lsp::Position::new(2, 8),
3191 lsp::Position::new(2, 17),
3192 ),
3193 },
3194 message: "original diagnostic".to_string(),
3195 }]),
3196 ..Default::default()
3197 },
3198 ],
3199 version: None,
3200 };
3201
3202 project
3203 .update(&mut cx, |p, cx| {
3204 p.update_diagnostics(message, &Default::default(), cx)
3205 })
3206 .unwrap();
3207 let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot());
3208
3209 assert_eq!(
3210 buffer
3211 .diagnostics_in_range::<_, Point>(0..buffer.len())
3212 .collect::<Vec<_>>(),
3213 &[
3214 DiagnosticEntry {
3215 range: Point::new(1, 8)..Point::new(1, 9),
3216 diagnostic: Diagnostic {
3217 severity: DiagnosticSeverity::WARNING,
3218 message: "error 1".to_string(),
3219 group_id: 0,
3220 is_primary: true,
3221 ..Default::default()
3222 }
3223 },
3224 DiagnosticEntry {
3225 range: Point::new(1, 8)..Point::new(1, 9),
3226 diagnostic: Diagnostic {
3227 severity: DiagnosticSeverity::HINT,
3228 message: "error 1 hint 1".to_string(),
3229 group_id: 0,
3230 is_primary: false,
3231 ..Default::default()
3232 }
3233 },
3234 DiagnosticEntry {
3235 range: Point::new(1, 13)..Point::new(1, 15),
3236 diagnostic: Diagnostic {
3237 severity: DiagnosticSeverity::HINT,
3238 message: "error 2 hint 1".to_string(),
3239 group_id: 1,
3240 is_primary: false,
3241 ..Default::default()
3242 }
3243 },
3244 DiagnosticEntry {
3245 range: Point::new(1, 13)..Point::new(1, 15),
3246 diagnostic: Diagnostic {
3247 severity: DiagnosticSeverity::HINT,
3248 message: "error 2 hint 2".to_string(),
3249 group_id: 1,
3250 is_primary: false,
3251 ..Default::default()
3252 }
3253 },
3254 DiagnosticEntry {
3255 range: Point::new(2, 8)..Point::new(2, 17),
3256 diagnostic: Diagnostic {
3257 severity: DiagnosticSeverity::ERROR,
3258 message: "error 2".to_string(),
3259 group_id: 1,
3260 is_primary: true,
3261 ..Default::default()
3262 }
3263 }
3264 ]
3265 );
3266
3267 assert_eq!(
3268 buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
3269 &[
3270 DiagnosticEntry {
3271 range: Point::new(1, 8)..Point::new(1, 9),
3272 diagnostic: Diagnostic {
3273 severity: DiagnosticSeverity::WARNING,
3274 message: "error 1".to_string(),
3275 group_id: 0,
3276 is_primary: true,
3277 ..Default::default()
3278 }
3279 },
3280 DiagnosticEntry {
3281 range: Point::new(1, 8)..Point::new(1, 9),
3282 diagnostic: Diagnostic {
3283 severity: DiagnosticSeverity::HINT,
3284 message: "error 1 hint 1".to_string(),
3285 group_id: 0,
3286 is_primary: false,
3287 ..Default::default()
3288 }
3289 },
3290 ]
3291 );
3292 assert_eq!(
3293 buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
3294 &[
3295 DiagnosticEntry {
3296 range: Point::new(1, 13)..Point::new(1, 15),
3297 diagnostic: Diagnostic {
3298 severity: DiagnosticSeverity::HINT,
3299 message: "error 2 hint 1".to_string(),
3300 group_id: 1,
3301 is_primary: false,
3302 ..Default::default()
3303 }
3304 },
3305 DiagnosticEntry {
3306 range: Point::new(1, 13)..Point::new(1, 15),
3307 diagnostic: Diagnostic {
3308 severity: DiagnosticSeverity::HINT,
3309 message: "error 2 hint 2".to_string(),
3310 group_id: 1,
3311 is_primary: false,
3312 ..Default::default()
3313 }
3314 },
3315 DiagnosticEntry {
3316 range: Point::new(2, 8)..Point::new(2, 17),
3317 diagnostic: Diagnostic {
3318 severity: DiagnosticSeverity::ERROR,
3319 message: "error 2".to_string(),
3320 group_id: 1,
3321 is_primary: true,
3322 ..Default::default()
3323 }
3324 }
3325 ]
3326 );
3327 }
3328
3329 fn build_project(fs: Arc<dyn Fs>, cx: &mut TestAppContext) -> ModelHandle<Project> {
3330 let languages = Arc::new(LanguageRegistry::new());
3331 let http_client = FakeHttpClient::with_404_response();
3332 let client = client::Client::new(http_client.clone());
3333 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
3334 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
3335 }
3336}