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, CodeAction, Diagnostic, DiagnosticEntry, File as _, Language,
19 LanguageRegistry, PointUtf16, ToLspPosition, ToOffset, ToPointUtf16,
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 apply_code_action(
1155 &self,
1156 buffer: ModelHandle<Buffer>,
1157 mut action: CodeAction<language::Anchor>,
1158 cx: &mut ModelContext<Self>,
1159 ) -> Task<Result<()>> {
1160 if self.is_local() {
1161 let buffer = buffer.read(cx);
1162 let server = if let Some(language_server) = buffer.language_server() {
1163 language_server.clone()
1164 } else {
1165 return Task::ready(Ok(Default::default()));
1166 };
1167 let position = action.position.to_point_utf16(buffer).to_lsp_position();
1168
1169 cx.spawn(|this, mut cx| async move {
1170 let range = action
1171 .lsp_action
1172 .data
1173 .as_mut()
1174 .and_then(|d| d.get_mut("codeActionParams"))
1175 .and_then(|d| d.get_mut("range"))
1176 .ok_or_else(|| anyhow!("code action has no range"))?;
1177 *range = serde_json::to_value(&lsp::Range::new(position, position)).unwrap();
1178 let action = server
1179 .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
1180 .await?;
1181 let edit = action
1182 .edit
1183 .ok_or_else(|| anyhow!("code action has no edit"));
1184 // match edit {
1185 // Ok(edit) => edit.,
1186 // Err(_) => todo!(),
1187 // }
1188 Ok(Default::default())
1189 })
1190 } else {
1191 log::info!("applying code actions is not implemented for guests");
1192 Task::ready(Ok(Default::default()))
1193 }
1194 // let file = if let Some(file) = self.file.as_ref() {
1195 // file
1196 // } else {
1197 // return Task::ready(Ok(Default::default()));
1198 // };
1199
1200 // if file.is_local() {
1201 // let server = if let Some(language_server) = self.language_server.as_ref() {
1202 // language_server.server.clone()
1203 // } else {
1204 // return Task::ready(Ok(Default::default()));
1205 // };
1206 // let position = action.position.to_point_utf16(self).to_lsp_position();
1207
1208 // cx.spawn(|this, mut cx| async move {
1209 // let range = action
1210 // .lsp_action
1211 // .data
1212 // .as_mut()
1213 // .and_then(|d| d.get_mut("codeActionParams"))
1214 // .and_then(|d| d.get_mut("range"))
1215 // .ok_or_else(|| anyhow!("code action has no range"))?;
1216 // *range = serde_json::to_value(&lsp::Range::new(position, position)).unwrap();
1217 // let action = server
1218 // .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
1219 // .await?;
1220 // let edit = action
1221 // .edit
1222 // .ok_or_else(|| anyhow!("code action has no edit"));
1223 // match edit {
1224 // Ok(edit) => edit.,
1225 // Err(_) => todo!(),
1226 // }
1227 // Ok(Default::default())
1228 // })
1229 // } else {
1230 // log::info!("applying code actions is not implemented for guests");
1231 // Task::ready(Ok(Default::default()))
1232 // }
1233 }
1234
1235 pub fn find_or_create_local_worktree(
1236 &self,
1237 abs_path: impl AsRef<Path>,
1238 weak: bool,
1239 cx: &mut ModelContext<Self>,
1240 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
1241 let abs_path = abs_path.as_ref();
1242 if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) {
1243 Task::ready(Ok((tree.clone(), relative_path.into())))
1244 } else {
1245 let worktree = self.create_local_worktree(abs_path, weak, cx);
1246 cx.foreground()
1247 .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
1248 }
1249 }
1250
1251 fn find_local_worktree(
1252 &self,
1253 abs_path: &Path,
1254 cx: &AppContext,
1255 ) -> Option<(ModelHandle<Worktree>, PathBuf)> {
1256 for tree in self.worktrees(cx) {
1257 if let Some(relative_path) = tree
1258 .read(cx)
1259 .as_local()
1260 .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
1261 {
1262 return Some((tree.clone(), relative_path.into()));
1263 }
1264 }
1265 None
1266 }
1267
1268 pub fn is_shared(&self) -> bool {
1269 match &self.client_state {
1270 ProjectClientState::Local { is_shared, .. } => *is_shared,
1271 ProjectClientState::Remote { .. } => false,
1272 }
1273 }
1274
1275 fn create_local_worktree(
1276 &self,
1277 abs_path: impl AsRef<Path>,
1278 weak: bool,
1279 cx: &mut ModelContext<Self>,
1280 ) -> Task<Result<ModelHandle<Worktree>>> {
1281 let fs = self.fs.clone();
1282 let client = self.client.clone();
1283 let path = Arc::from(abs_path.as_ref());
1284 cx.spawn(|project, mut cx| async move {
1285 let worktree = Worktree::local(client.clone(), path, weak, fs, &mut cx).await?;
1286
1287 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
1288 project.add_worktree(&worktree, cx);
1289 (project.remote_id(), project.is_shared())
1290 });
1291
1292 if let Some(project_id) = remote_project_id {
1293 worktree
1294 .update(&mut cx, |worktree, cx| {
1295 worktree.as_local_mut().unwrap().register(project_id, cx)
1296 })
1297 .await?;
1298 if is_shared {
1299 worktree
1300 .update(&mut cx, |worktree, cx| {
1301 worktree.as_local_mut().unwrap().share(project_id, cx)
1302 })
1303 .await?;
1304 }
1305 }
1306
1307 Ok(worktree)
1308 })
1309 }
1310
1311 pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext<Self>) {
1312 self.worktrees.retain(|worktree| {
1313 worktree
1314 .upgrade(cx)
1315 .map_or(false, |w| w.read(cx).id() != id)
1316 });
1317 cx.notify();
1318 }
1319
1320 fn add_worktree(&mut self, worktree: &ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
1321 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
1322 if worktree.read(cx).is_local() {
1323 cx.subscribe(&worktree, |this, worktree, _, cx| {
1324 this.update_local_worktree_buffers(worktree, cx);
1325 })
1326 .detach();
1327 }
1328
1329 let push_weak_handle = {
1330 let worktree = worktree.read(cx);
1331 worktree.is_local() && worktree.is_weak()
1332 };
1333 if push_weak_handle {
1334 cx.observe_release(&worktree, |this, cx| {
1335 this.worktrees
1336 .retain(|worktree| worktree.upgrade(cx).is_some());
1337 cx.notify();
1338 })
1339 .detach();
1340 self.worktrees
1341 .push(WorktreeHandle::Weak(worktree.downgrade()));
1342 } else {
1343 self.worktrees
1344 .push(WorktreeHandle::Strong(worktree.clone()));
1345 }
1346 cx.notify();
1347 }
1348
1349 fn update_local_worktree_buffers(
1350 &mut self,
1351 worktree_handle: ModelHandle<Worktree>,
1352 cx: &mut ModelContext<Self>,
1353 ) {
1354 let snapshot = worktree_handle.read(cx).snapshot();
1355 let mut buffers_to_delete = Vec::new();
1356 for (buffer_id, buffer) in &self.open_buffers {
1357 if let Some(buffer) = buffer.upgrade(cx) {
1358 buffer.update(cx, |buffer, cx| {
1359 if let Some(old_file) = File::from_dyn(buffer.file()) {
1360 if old_file.worktree != worktree_handle {
1361 return;
1362 }
1363
1364 let new_file = if let Some(entry) = old_file
1365 .entry_id
1366 .and_then(|entry_id| snapshot.entry_for_id(entry_id))
1367 {
1368 File {
1369 is_local: true,
1370 entry_id: Some(entry.id),
1371 mtime: entry.mtime,
1372 path: entry.path.clone(),
1373 worktree: worktree_handle.clone(),
1374 }
1375 } else if let Some(entry) =
1376 snapshot.entry_for_path(old_file.path().as_ref())
1377 {
1378 File {
1379 is_local: true,
1380 entry_id: Some(entry.id),
1381 mtime: entry.mtime,
1382 path: entry.path.clone(),
1383 worktree: worktree_handle.clone(),
1384 }
1385 } else {
1386 File {
1387 is_local: true,
1388 entry_id: None,
1389 path: old_file.path().clone(),
1390 mtime: old_file.mtime(),
1391 worktree: worktree_handle.clone(),
1392 }
1393 };
1394
1395 if let Some(project_id) = self.remote_id() {
1396 let client = self.client.clone();
1397 let message = proto::UpdateBufferFile {
1398 project_id,
1399 buffer_id: *buffer_id as u64,
1400 file: Some(new_file.to_proto()),
1401 };
1402 cx.foreground()
1403 .spawn(async move { client.send(message).await })
1404 .detach_and_log_err(cx);
1405 }
1406 buffer.file_updated(Box::new(new_file), cx).detach();
1407 }
1408 });
1409 } else {
1410 buffers_to_delete.push(*buffer_id);
1411 }
1412 }
1413
1414 for buffer_id in buffers_to_delete {
1415 self.open_buffers.remove(&buffer_id);
1416 }
1417 }
1418
1419 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
1420 let new_active_entry = entry.and_then(|project_path| {
1421 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
1422 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
1423 Some(ProjectEntry {
1424 worktree_id: project_path.worktree_id,
1425 entry_id: entry.id,
1426 })
1427 });
1428 if new_active_entry != self.active_entry {
1429 self.active_entry = new_active_entry;
1430 cx.emit(Event::ActiveEntryChanged(new_active_entry));
1431 }
1432 }
1433
1434 pub fn is_running_disk_based_diagnostics(&self) -> bool {
1435 self.language_servers_with_diagnostics_running > 0
1436 }
1437
1438 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
1439 let mut summary = DiagnosticSummary::default();
1440 for (_, path_summary) in self.diagnostic_summaries(cx) {
1441 summary.error_count += path_summary.error_count;
1442 summary.warning_count += path_summary.warning_count;
1443 summary.info_count += path_summary.info_count;
1444 summary.hint_count += path_summary.hint_count;
1445 }
1446 summary
1447 }
1448
1449 pub fn diagnostic_summaries<'a>(
1450 &'a self,
1451 cx: &'a AppContext,
1452 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
1453 self.worktrees(cx).flat_map(move |worktree| {
1454 let worktree = worktree.read(cx);
1455 let worktree_id = worktree.id();
1456 worktree
1457 .diagnostic_summaries()
1458 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
1459 })
1460 }
1461
1462 pub fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext<Self>) {
1463 self.language_servers_with_diagnostics_running += 1;
1464 if self.language_servers_with_diagnostics_running == 1 {
1465 cx.emit(Event::DiskBasedDiagnosticsStarted);
1466 }
1467 }
1468
1469 pub fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext<Self>) {
1470 cx.emit(Event::DiskBasedDiagnosticsUpdated);
1471 self.language_servers_with_diagnostics_running -= 1;
1472 if self.language_servers_with_diagnostics_running == 0 {
1473 cx.emit(Event::DiskBasedDiagnosticsFinished);
1474 }
1475 }
1476
1477 pub fn active_entry(&self) -> Option<ProjectEntry> {
1478 self.active_entry
1479 }
1480
1481 // RPC message handlers
1482
1483 fn handle_unshare_project(
1484 &mut self,
1485 _: TypedEnvelope<proto::UnshareProject>,
1486 _: Arc<Client>,
1487 cx: &mut ModelContext<Self>,
1488 ) -> Result<()> {
1489 if let ProjectClientState::Remote {
1490 sharing_has_stopped,
1491 ..
1492 } = &mut self.client_state
1493 {
1494 *sharing_has_stopped = true;
1495 self.collaborators.clear();
1496 cx.notify();
1497 Ok(())
1498 } else {
1499 unreachable!()
1500 }
1501 }
1502
1503 fn handle_add_collaborator(
1504 &mut self,
1505 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
1506 _: Arc<Client>,
1507 cx: &mut ModelContext<Self>,
1508 ) -> Result<()> {
1509 let user_store = self.user_store.clone();
1510 let collaborator = envelope
1511 .payload
1512 .collaborator
1513 .take()
1514 .ok_or_else(|| anyhow!("empty collaborator"))?;
1515
1516 cx.spawn(|this, mut cx| {
1517 async move {
1518 let collaborator =
1519 Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
1520 this.update(&mut cx, |this, cx| {
1521 this.collaborators
1522 .insert(collaborator.peer_id, collaborator);
1523 cx.notify();
1524 });
1525 Ok(())
1526 }
1527 .log_err()
1528 })
1529 .detach();
1530
1531 Ok(())
1532 }
1533
1534 fn handle_remove_collaborator(
1535 &mut self,
1536 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
1537 _: Arc<Client>,
1538 cx: &mut ModelContext<Self>,
1539 ) -> Result<()> {
1540 let peer_id = PeerId(envelope.payload.peer_id);
1541 let replica_id = self
1542 .collaborators
1543 .remove(&peer_id)
1544 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
1545 .replica_id;
1546 self.shared_buffers.remove(&peer_id);
1547 for (_, buffer) in &self.open_buffers {
1548 if let Some(buffer) = buffer.upgrade(cx) {
1549 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
1550 }
1551 }
1552 cx.notify();
1553 Ok(())
1554 }
1555
1556 fn handle_share_worktree(
1557 &mut self,
1558 envelope: TypedEnvelope<proto::ShareWorktree>,
1559 client: Arc<Client>,
1560 cx: &mut ModelContext<Self>,
1561 ) -> Result<()> {
1562 let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
1563 let replica_id = self.replica_id();
1564 let worktree = envelope
1565 .payload
1566 .worktree
1567 .ok_or_else(|| anyhow!("invalid worktree"))?;
1568 let (worktree, load_task) = Worktree::remote(remote_id, replica_id, worktree, client, cx);
1569 self.add_worktree(&worktree, cx);
1570 load_task.detach();
1571 Ok(())
1572 }
1573
1574 fn handle_unregister_worktree(
1575 &mut self,
1576 envelope: TypedEnvelope<proto::UnregisterWorktree>,
1577 _: Arc<Client>,
1578 cx: &mut ModelContext<Self>,
1579 ) -> Result<()> {
1580 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1581 self.remove_worktree(worktree_id, cx);
1582 Ok(())
1583 }
1584
1585 fn handle_update_worktree(
1586 &mut self,
1587 envelope: TypedEnvelope<proto::UpdateWorktree>,
1588 _: Arc<Client>,
1589 cx: &mut ModelContext<Self>,
1590 ) -> Result<()> {
1591 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1592 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1593 worktree.update(cx, |worktree, cx| {
1594 let worktree = worktree.as_remote_mut().unwrap();
1595 worktree.update_from_remote(envelope, cx)
1596 })?;
1597 }
1598 Ok(())
1599 }
1600
1601 fn handle_update_diagnostic_summary(
1602 &mut self,
1603 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
1604 _: Arc<Client>,
1605 cx: &mut ModelContext<Self>,
1606 ) -> Result<()> {
1607 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1608 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1609 if let Some(summary) = envelope.payload.summary {
1610 let project_path = ProjectPath {
1611 worktree_id,
1612 path: Path::new(&summary.path).into(),
1613 };
1614 worktree.update(cx, |worktree, _| {
1615 worktree
1616 .as_remote_mut()
1617 .unwrap()
1618 .update_diagnostic_summary(project_path.path.clone(), &summary);
1619 });
1620 cx.emit(Event::DiagnosticsUpdated(project_path));
1621 }
1622 }
1623 Ok(())
1624 }
1625
1626 fn handle_disk_based_diagnostics_updating(
1627 &mut self,
1628 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
1629 _: Arc<Client>,
1630 cx: &mut ModelContext<Self>,
1631 ) -> Result<()> {
1632 self.disk_based_diagnostics_started(cx);
1633 Ok(())
1634 }
1635
1636 fn handle_disk_based_diagnostics_updated(
1637 &mut self,
1638 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
1639 _: Arc<Client>,
1640 cx: &mut ModelContext<Self>,
1641 ) -> Result<()> {
1642 self.disk_based_diagnostics_finished(cx);
1643 Ok(())
1644 }
1645
1646 pub fn handle_update_buffer(
1647 &mut self,
1648 envelope: TypedEnvelope<proto::UpdateBuffer>,
1649 _: Arc<Client>,
1650 cx: &mut ModelContext<Self>,
1651 ) -> Result<()> {
1652 let payload = envelope.payload.clone();
1653 let buffer_id = payload.buffer_id as usize;
1654 let ops = payload
1655 .operations
1656 .into_iter()
1657 .map(|op| language::proto::deserialize_operation(op))
1658 .collect::<Result<Vec<_>, _>>()?;
1659 if let Some(buffer) = self.open_buffers.get_mut(&buffer_id) {
1660 if let Some(buffer) = buffer.upgrade(cx) {
1661 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
1662 }
1663 }
1664 Ok(())
1665 }
1666
1667 pub fn handle_update_buffer_file(
1668 &mut self,
1669 envelope: TypedEnvelope<proto::UpdateBufferFile>,
1670 _: Arc<Client>,
1671 cx: &mut ModelContext<Self>,
1672 ) -> Result<()> {
1673 let payload = envelope.payload.clone();
1674 let buffer_id = payload.buffer_id as usize;
1675 let file = payload.file.ok_or_else(|| anyhow!("invalid file"))?;
1676 let worktree = self
1677 .worktree_for_id(WorktreeId::from_proto(file.worktree_id), cx)
1678 .ok_or_else(|| anyhow!("no such worktree"))?;
1679 let file = File::from_proto(file, worktree.clone(), cx)?;
1680 let buffer = self
1681 .open_buffers
1682 .get_mut(&buffer_id)
1683 .and_then(|b| b.upgrade(cx))
1684 .ok_or_else(|| anyhow!("no such buffer"))?;
1685 buffer.update(cx, |buffer, cx| {
1686 buffer.file_updated(Box::new(file), cx).detach();
1687 });
1688
1689 Ok(())
1690 }
1691
1692 pub fn handle_save_buffer(
1693 &mut self,
1694 envelope: TypedEnvelope<proto::SaveBuffer>,
1695 rpc: Arc<Client>,
1696 cx: &mut ModelContext<Self>,
1697 ) -> Result<()> {
1698 let sender_id = envelope.original_sender_id()?;
1699 let project_id = self.remote_id().ok_or_else(|| anyhow!("not connected"))?;
1700 let buffer = self
1701 .shared_buffers
1702 .get(&sender_id)
1703 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1704 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1705 let receipt = envelope.receipt();
1706 let buffer_id = envelope.payload.buffer_id;
1707 let save = cx.spawn(|_, mut cx| async move {
1708 buffer.update(&mut cx, |buffer, cx| buffer.save(cx)).await
1709 });
1710
1711 cx.background()
1712 .spawn(
1713 async move {
1714 let (version, mtime) = save.await?;
1715
1716 rpc.respond(
1717 receipt,
1718 proto::BufferSaved {
1719 project_id,
1720 buffer_id,
1721 version: (&version).into(),
1722 mtime: Some(mtime.into()),
1723 },
1724 )
1725 .await?;
1726
1727 Ok(())
1728 }
1729 .log_err(),
1730 )
1731 .detach();
1732 Ok(())
1733 }
1734
1735 pub fn handle_format_buffer(
1736 &mut self,
1737 envelope: TypedEnvelope<proto::FormatBuffer>,
1738 rpc: Arc<Client>,
1739 cx: &mut ModelContext<Self>,
1740 ) -> Result<()> {
1741 let receipt = envelope.receipt();
1742 let sender_id = envelope.original_sender_id()?;
1743 let buffer = self
1744 .shared_buffers
1745 .get(&sender_id)
1746 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1747 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1748 cx.spawn(|_, mut cx| async move {
1749 let format = buffer.update(&mut cx, |buffer, cx| buffer.format(cx)).await;
1750 // We spawn here in order to enqueue the sending of `Ack` *after* transmission of edits
1751 // associated with formatting.
1752 cx.spawn(|_| async move {
1753 match format {
1754 Ok(()) => rpc.respond(receipt, proto::Ack {}).await?,
1755 Err(error) => {
1756 rpc.respond_with_error(
1757 receipt,
1758 proto::Error {
1759 message: error.to_string(),
1760 },
1761 )
1762 .await?
1763 }
1764 }
1765 Ok::<_, anyhow::Error>(())
1766 })
1767 .await
1768 .log_err();
1769 })
1770 .detach();
1771 Ok(())
1772 }
1773
1774 fn handle_get_completions(
1775 &mut self,
1776 envelope: TypedEnvelope<proto::GetCompletions>,
1777 rpc: Arc<Client>,
1778 cx: &mut ModelContext<Self>,
1779 ) -> Result<()> {
1780 let receipt = envelope.receipt();
1781 let sender_id = envelope.original_sender_id()?;
1782 let buffer = self
1783 .shared_buffers
1784 .get(&sender_id)
1785 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1786 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1787 let position = envelope
1788 .payload
1789 .position
1790 .and_then(language::proto::deserialize_anchor)
1791 .ok_or_else(|| anyhow!("invalid position"))?;
1792 cx.spawn(|_, mut cx| async move {
1793 match buffer
1794 .update(&mut cx, |buffer, cx| buffer.completions(position, cx))
1795 .await
1796 {
1797 Ok(completions) => {
1798 rpc.respond(
1799 receipt,
1800 proto::GetCompletionsResponse {
1801 completions: completions
1802 .iter()
1803 .map(language::proto::serialize_completion)
1804 .collect(),
1805 },
1806 )
1807 .await
1808 }
1809 Err(error) => {
1810 rpc.respond_with_error(
1811 receipt,
1812 proto::Error {
1813 message: error.to_string(),
1814 },
1815 )
1816 .await
1817 }
1818 }
1819 })
1820 .detach_and_log_err(cx);
1821 Ok(())
1822 }
1823
1824 fn handle_apply_additional_edits_for_completion(
1825 &mut self,
1826 envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
1827 rpc: Arc<Client>,
1828 cx: &mut ModelContext<Self>,
1829 ) -> Result<()> {
1830 let receipt = envelope.receipt();
1831 let sender_id = envelope.original_sender_id()?;
1832 let buffer = self
1833 .shared_buffers
1834 .get(&sender_id)
1835 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1836 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1837 let language = buffer.read(cx).language();
1838 let completion = language::proto::deserialize_completion(
1839 envelope
1840 .payload
1841 .completion
1842 .ok_or_else(|| anyhow!("invalid position"))?,
1843 language,
1844 )?;
1845 cx.spawn(|_, mut cx| async move {
1846 match buffer
1847 .update(&mut cx, |buffer, cx| {
1848 buffer.apply_additional_edits_for_completion(completion, false, cx)
1849 })
1850 .await
1851 {
1852 Ok(edit_ids) => {
1853 rpc.respond(
1854 receipt,
1855 proto::ApplyCompletionAdditionalEditsResponse {
1856 additional_edits: edit_ids
1857 .into_iter()
1858 .map(|edit_id| proto::AdditionalEdit {
1859 replica_id: edit_id.replica_id as u32,
1860 local_timestamp: edit_id.value,
1861 })
1862 .collect(),
1863 },
1864 )
1865 .await
1866 }
1867 Err(error) => {
1868 rpc.respond_with_error(
1869 receipt,
1870 proto::Error {
1871 message: error.to_string(),
1872 },
1873 )
1874 .await
1875 }
1876 }
1877 })
1878 .detach_and_log_err(cx);
1879 Ok(())
1880 }
1881
1882 pub fn handle_get_definition(
1883 &mut self,
1884 envelope: TypedEnvelope<proto::GetDefinition>,
1885 rpc: Arc<Client>,
1886 cx: &mut ModelContext<Self>,
1887 ) -> Result<()> {
1888 let receipt = envelope.receipt();
1889 let sender_id = envelope.original_sender_id()?;
1890 let source_buffer = self
1891 .shared_buffers
1892 .get(&sender_id)
1893 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1894 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1895 let position = envelope
1896 .payload
1897 .position
1898 .and_then(deserialize_anchor)
1899 .ok_or_else(|| anyhow!("invalid position"))?;
1900 if !source_buffer.read(cx).can_resolve(&position) {
1901 return Err(anyhow!("cannot resolve position"));
1902 }
1903
1904 let definitions = self.definition(&source_buffer, position, cx);
1905 cx.spawn(|this, mut cx| async move {
1906 let definitions = definitions.await?;
1907 let mut response = proto::GetDefinitionResponse {
1908 definitions: Default::default(),
1909 };
1910 this.update(&mut cx, |this, cx| {
1911 for definition in definitions {
1912 let buffer =
1913 this.serialize_buffer_for_peer(&definition.target_buffer, sender_id, cx);
1914 response.definitions.push(proto::Definition {
1915 target_start: Some(serialize_anchor(&definition.target_range.start)),
1916 target_end: Some(serialize_anchor(&definition.target_range.end)),
1917 buffer: Some(buffer),
1918 });
1919 }
1920 });
1921 rpc.respond(receipt, response).await?;
1922 Ok::<_, anyhow::Error>(())
1923 })
1924 .detach_and_log_err(cx);
1925
1926 Ok(())
1927 }
1928
1929 pub fn handle_open_buffer(
1930 &mut self,
1931 envelope: TypedEnvelope<proto::OpenBuffer>,
1932 rpc: Arc<Client>,
1933 cx: &mut ModelContext<Self>,
1934 ) -> anyhow::Result<()> {
1935 let receipt = envelope.receipt();
1936 let peer_id = envelope.original_sender_id()?;
1937 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1938 let open_buffer = self.open_buffer(
1939 ProjectPath {
1940 worktree_id,
1941 path: PathBuf::from(envelope.payload.path).into(),
1942 },
1943 cx,
1944 );
1945 cx.spawn(|this, mut cx| {
1946 async move {
1947 let buffer = open_buffer.await?;
1948 let buffer = this.update(&mut cx, |this, cx| {
1949 this.serialize_buffer_for_peer(&buffer, peer_id, cx)
1950 });
1951 rpc.respond(
1952 receipt,
1953 proto::OpenBufferResponse {
1954 buffer: Some(buffer),
1955 },
1956 )
1957 .await
1958 }
1959 .log_err()
1960 })
1961 .detach();
1962 Ok(())
1963 }
1964
1965 fn serialize_buffer_for_peer(
1966 &mut self,
1967 buffer: &ModelHandle<Buffer>,
1968 peer_id: PeerId,
1969 cx: &AppContext,
1970 ) -> proto::Buffer {
1971 let buffer_id = buffer.read(cx).remote_id();
1972 let shared_buffers = self.shared_buffers.entry(peer_id).or_default();
1973 match shared_buffers.entry(buffer_id) {
1974 hash_map::Entry::Occupied(_) => proto::Buffer {
1975 variant: Some(proto::buffer::Variant::Id(buffer_id)),
1976 },
1977 hash_map::Entry::Vacant(entry) => {
1978 entry.insert(buffer.clone());
1979 proto::Buffer {
1980 variant: Some(proto::buffer::Variant::State(buffer.read(cx).to_proto())),
1981 }
1982 }
1983 }
1984 }
1985
1986 fn deserialize_remote_buffer(
1987 &mut self,
1988 buffer: proto::Buffer,
1989 cx: &mut ModelContext<Self>,
1990 ) -> Result<ModelHandle<Buffer>> {
1991 match buffer.variant.ok_or_else(|| anyhow!("missing buffer"))? {
1992 proto::buffer::Variant::Id(id) => self
1993 .open_buffers
1994 .get(&(id as usize))
1995 .and_then(|buffer| buffer.upgrade(cx))
1996 .ok_or_else(|| anyhow!("no buffer exists for id {}", id)),
1997 proto::buffer::Variant::State(mut buffer) => {
1998 let mut buffer_worktree = None;
1999 let mut buffer_file = None;
2000 if let Some(file) = buffer.file.take() {
2001 let worktree_id = WorktreeId::from_proto(file.worktree_id);
2002 let worktree = self
2003 .worktree_for_id(worktree_id, cx)
2004 .ok_or_else(|| anyhow!("no worktree found for id {}", file.worktree_id))?;
2005 buffer_file = Some(Box::new(File::from_proto(file, worktree.clone(), cx)?)
2006 as Box<dyn language::File>);
2007 buffer_worktree = Some(worktree);
2008 }
2009
2010 let buffer = cx.add_model(|cx| {
2011 Buffer::from_proto(self.replica_id(), buffer, buffer_file, cx).unwrap()
2012 });
2013 self.register_buffer(&buffer, buffer_worktree.as_ref(), cx)?;
2014 Ok(buffer)
2015 }
2016 }
2017 }
2018
2019 pub fn handle_close_buffer(
2020 &mut self,
2021 envelope: TypedEnvelope<proto::CloseBuffer>,
2022 _: Arc<Client>,
2023 cx: &mut ModelContext<Self>,
2024 ) -> anyhow::Result<()> {
2025 if let Some(shared_buffers) = self.shared_buffers.get_mut(&envelope.original_sender_id()?) {
2026 shared_buffers.remove(&envelope.payload.buffer_id);
2027 cx.notify();
2028 }
2029 Ok(())
2030 }
2031
2032 pub fn handle_buffer_saved(
2033 &mut self,
2034 envelope: TypedEnvelope<proto::BufferSaved>,
2035 _: Arc<Client>,
2036 cx: &mut ModelContext<Self>,
2037 ) -> Result<()> {
2038 let payload = envelope.payload.clone();
2039 let buffer = self
2040 .open_buffers
2041 .get(&(payload.buffer_id as usize))
2042 .and_then(|buffer| buffer.upgrade(cx));
2043 if let Some(buffer) = buffer {
2044 buffer.update(cx, |buffer, cx| {
2045 let version = payload.version.try_into()?;
2046 let mtime = payload
2047 .mtime
2048 .ok_or_else(|| anyhow!("missing mtime"))?
2049 .into();
2050 buffer.did_save(version, mtime, None, cx);
2051 Result::<_, anyhow::Error>::Ok(())
2052 })?;
2053 }
2054 Ok(())
2055 }
2056
2057 pub fn handle_buffer_reloaded(
2058 &mut self,
2059 envelope: TypedEnvelope<proto::BufferReloaded>,
2060 _: Arc<Client>,
2061 cx: &mut ModelContext<Self>,
2062 ) -> Result<()> {
2063 let payload = envelope.payload.clone();
2064 let buffer = self
2065 .open_buffers
2066 .get(&(payload.buffer_id as usize))
2067 .and_then(|buffer| buffer.upgrade(cx));
2068 if let Some(buffer) = buffer {
2069 buffer.update(cx, |buffer, cx| {
2070 let version = payload.version.try_into()?;
2071 let mtime = payload
2072 .mtime
2073 .ok_or_else(|| anyhow!("missing mtime"))?
2074 .into();
2075 buffer.did_reload(version, mtime, cx);
2076 Result::<_, anyhow::Error>::Ok(())
2077 })?;
2078 }
2079 Ok(())
2080 }
2081
2082 pub fn match_paths<'a>(
2083 &self,
2084 query: &'a str,
2085 include_ignored: bool,
2086 smart_case: bool,
2087 max_results: usize,
2088 cancel_flag: &'a AtomicBool,
2089 cx: &AppContext,
2090 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
2091 let worktrees = self
2092 .worktrees(cx)
2093 .filter(|worktree| !worktree.read(cx).is_weak())
2094 .collect::<Vec<_>>();
2095 let include_root_name = worktrees.len() > 1;
2096 let candidate_sets = worktrees
2097 .into_iter()
2098 .map(|worktree| CandidateSet {
2099 snapshot: worktree.read(cx).snapshot(),
2100 include_ignored,
2101 include_root_name,
2102 })
2103 .collect::<Vec<_>>();
2104
2105 let background = cx.background().clone();
2106 async move {
2107 fuzzy::match_paths(
2108 candidate_sets.as_slice(),
2109 query,
2110 smart_case,
2111 max_results,
2112 cancel_flag,
2113 background,
2114 )
2115 .await
2116 }
2117 }
2118}
2119
2120impl WorktreeHandle {
2121 pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
2122 match self {
2123 WorktreeHandle::Strong(handle) => Some(handle.clone()),
2124 WorktreeHandle::Weak(handle) => handle.upgrade(cx),
2125 }
2126 }
2127}
2128
2129struct CandidateSet {
2130 snapshot: Snapshot,
2131 include_ignored: bool,
2132 include_root_name: bool,
2133}
2134
2135impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
2136 type Candidates = CandidateSetIter<'a>;
2137
2138 fn id(&self) -> usize {
2139 self.snapshot.id().to_usize()
2140 }
2141
2142 fn len(&self) -> usize {
2143 if self.include_ignored {
2144 self.snapshot.file_count()
2145 } else {
2146 self.snapshot.visible_file_count()
2147 }
2148 }
2149
2150 fn prefix(&self) -> Arc<str> {
2151 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
2152 self.snapshot.root_name().into()
2153 } else if self.include_root_name {
2154 format!("{}/", self.snapshot.root_name()).into()
2155 } else {
2156 "".into()
2157 }
2158 }
2159
2160 fn candidates(&'a self, start: usize) -> Self::Candidates {
2161 CandidateSetIter {
2162 traversal: self.snapshot.files(self.include_ignored, start),
2163 }
2164 }
2165}
2166
2167struct CandidateSetIter<'a> {
2168 traversal: Traversal<'a>,
2169}
2170
2171impl<'a> Iterator for CandidateSetIter<'a> {
2172 type Item = PathMatchCandidate<'a>;
2173
2174 fn next(&mut self) -> Option<Self::Item> {
2175 self.traversal.next().map(|entry| {
2176 if let EntryKind::File(char_bag) = entry.kind {
2177 PathMatchCandidate {
2178 path: &entry.path,
2179 char_bag,
2180 }
2181 } else {
2182 unreachable!()
2183 }
2184 })
2185 }
2186}
2187
2188impl Entity for Project {
2189 type Event = Event;
2190
2191 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
2192 match &self.client_state {
2193 ProjectClientState::Local { remote_id_rx, .. } => {
2194 if let Some(project_id) = *remote_id_rx.borrow() {
2195 let rpc = self.client.clone();
2196 cx.spawn(|_| async move {
2197 if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
2198 log::error!("error unregistering project: {}", err);
2199 }
2200 })
2201 .detach();
2202 }
2203 }
2204 ProjectClientState::Remote { remote_id, .. } => {
2205 let rpc = self.client.clone();
2206 let project_id = *remote_id;
2207 cx.spawn(|_| async move {
2208 if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
2209 log::error!("error leaving project: {}", err);
2210 }
2211 })
2212 .detach();
2213 }
2214 }
2215 }
2216
2217 fn app_will_quit(
2218 &mut self,
2219 _: &mut MutableAppContext,
2220 ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
2221 use futures::FutureExt;
2222
2223 let shutdown_futures = self
2224 .language_servers
2225 .drain()
2226 .filter_map(|(_, server)| server.shutdown())
2227 .collect::<Vec<_>>();
2228 Some(
2229 async move {
2230 futures::future::join_all(shutdown_futures).await;
2231 }
2232 .boxed(),
2233 )
2234 }
2235}
2236
2237impl Collaborator {
2238 fn from_proto(
2239 message: proto::Collaborator,
2240 user_store: &ModelHandle<UserStore>,
2241 cx: &mut AsyncAppContext,
2242 ) -> impl Future<Output = Result<Self>> {
2243 let user = user_store.update(cx, |user_store, cx| {
2244 user_store.fetch_user(message.user_id, cx)
2245 });
2246
2247 async move {
2248 Ok(Self {
2249 peer_id: PeerId(message.peer_id),
2250 user: user.await?,
2251 replica_id: message.replica_id as ReplicaId,
2252 })
2253 }
2254 }
2255}
2256
2257impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
2258 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
2259 Self {
2260 worktree_id,
2261 path: path.as_ref().into(),
2262 }
2263 }
2264}
2265
2266#[cfg(test)]
2267mod tests {
2268 use super::{Event, *};
2269 use client::test::FakeHttpClient;
2270 use fs::RealFs;
2271 use futures::StreamExt;
2272 use gpui::{test::subscribe, TestAppContext};
2273 use language::{
2274 tree_sitter_rust, AnchorRangeExt, Diagnostic, LanguageConfig, LanguageRegistry,
2275 LanguageServerConfig, Point,
2276 };
2277 use lsp::Url;
2278 use serde_json::json;
2279 use std::{cell::RefCell, os::unix, path::PathBuf, rc::Rc};
2280 use unindent::Unindent as _;
2281 use util::test::temp_tree;
2282 use worktree::WorktreeHandle as _;
2283
2284 #[gpui::test]
2285 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
2286 let dir = temp_tree(json!({
2287 "root": {
2288 "apple": "",
2289 "banana": {
2290 "carrot": {
2291 "date": "",
2292 "endive": "",
2293 }
2294 },
2295 "fennel": {
2296 "grape": "",
2297 }
2298 }
2299 }));
2300
2301 let root_link_path = dir.path().join("root_link");
2302 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
2303 unix::fs::symlink(
2304 &dir.path().join("root/fennel"),
2305 &dir.path().join("root/finnochio"),
2306 )
2307 .unwrap();
2308
2309 let project = build_project(Arc::new(RealFs), &mut cx);
2310
2311 let (tree, _) = project
2312 .update(&mut cx, |project, cx| {
2313 project.find_or_create_local_worktree(&root_link_path, false, cx)
2314 })
2315 .await
2316 .unwrap();
2317
2318 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2319 .await;
2320 cx.read(|cx| {
2321 let tree = tree.read(cx);
2322 assert_eq!(tree.file_count(), 5);
2323 assert_eq!(
2324 tree.inode_for_path("fennel/grape"),
2325 tree.inode_for_path("finnochio/grape")
2326 );
2327 });
2328
2329 let cancel_flag = Default::default();
2330 let results = project
2331 .read_with(&cx, |project, cx| {
2332 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
2333 })
2334 .await;
2335 assert_eq!(
2336 results
2337 .into_iter()
2338 .map(|result| result.path)
2339 .collect::<Vec<Arc<Path>>>(),
2340 vec![
2341 PathBuf::from("banana/carrot/date").into(),
2342 PathBuf::from("banana/carrot/endive").into(),
2343 ]
2344 );
2345 }
2346
2347 #[gpui::test]
2348 async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
2349 let (language_server_config, mut fake_server) =
2350 LanguageServerConfig::fake(cx.background()).await;
2351 let progress_token = language_server_config
2352 .disk_based_diagnostics_progress_token
2353 .clone()
2354 .unwrap();
2355
2356 let mut languages = LanguageRegistry::new();
2357 languages.add(Arc::new(Language::new(
2358 LanguageConfig {
2359 name: "Rust".to_string(),
2360 path_suffixes: vec!["rs".to_string()],
2361 language_server: Some(language_server_config),
2362 ..Default::default()
2363 },
2364 Some(tree_sitter_rust::language()),
2365 )));
2366
2367 let dir = temp_tree(json!({
2368 "a.rs": "fn a() { A }",
2369 "b.rs": "const y: i32 = 1",
2370 }));
2371
2372 let http_client = FakeHttpClient::with_404_response();
2373 let client = Client::new(http_client.clone());
2374 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
2375
2376 let project = cx.update(|cx| {
2377 Project::local(
2378 client,
2379 user_store,
2380 Arc::new(languages),
2381 Arc::new(RealFs),
2382 cx,
2383 )
2384 });
2385
2386 let (tree, _) = project
2387 .update(&mut cx, |project, cx| {
2388 project.find_or_create_local_worktree(dir.path(), false, cx)
2389 })
2390 .await
2391 .unwrap();
2392 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
2393
2394 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2395 .await;
2396
2397 // Cause worktree to start the fake language server
2398 let _buffer = project
2399 .update(&mut cx, |project, cx| {
2400 project.open_buffer(
2401 ProjectPath {
2402 worktree_id,
2403 path: Path::new("b.rs").into(),
2404 },
2405 cx,
2406 )
2407 })
2408 .await
2409 .unwrap();
2410
2411 let mut events = subscribe(&project, &mut cx);
2412
2413 fake_server.start_progress(&progress_token).await;
2414 assert_eq!(
2415 events.next().await.unwrap(),
2416 Event::DiskBasedDiagnosticsStarted
2417 );
2418
2419 fake_server.start_progress(&progress_token).await;
2420 fake_server.end_progress(&progress_token).await;
2421 fake_server.start_progress(&progress_token).await;
2422
2423 fake_server
2424 .notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
2425 uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
2426 version: None,
2427 diagnostics: vec![lsp::Diagnostic {
2428 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
2429 severity: Some(lsp::DiagnosticSeverity::ERROR),
2430 message: "undefined variable 'A'".to_string(),
2431 ..Default::default()
2432 }],
2433 })
2434 .await;
2435 assert_eq!(
2436 events.next().await.unwrap(),
2437 Event::DiagnosticsUpdated(ProjectPath {
2438 worktree_id,
2439 path: Arc::from(Path::new("a.rs"))
2440 })
2441 );
2442
2443 fake_server.end_progress(&progress_token).await;
2444 fake_server.end_progress(&progress_token).await;
2445 assert_eq!(
2446 events.next().await.unwrap(),
2447 Event::DiskBasedDiagnosticsUpdated
2448 );
2449 assert_eq!(
2450 events.next().await.unwrap(),
2451 Event::DiskBasedDiagnosticsFinished
2452 );
2453
2454 let buffer = project
2455 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
2456 .await
2457 .unwrap();
2458
2459 buffer.read_with(&cx, |buffer, _| {
2460 let snapshot = buffer.snapshot();
2461 let diagnostics = snapshot
2462 .diagnostics_in_range::<_, Point>(0..buffer.len())
2463 .collect::<Vec<_>>();
2464 assert_eq!(
2465 diagnostics,
2466 &[DiagnosticEntry {
2467 range: Point::new(0, 9)..Point::new(0, 10),
2468 diagnostic: Diagnostic {
2469 severity: lsp::DiagnosticSeverity::ERROR,
2470 message: "undefined variable 'A'".to_string(),
2471 group_id: 0,
2472 is_primary: true,
2473 ..Default::default()
2474 }
2475 }]
2476 )
2477 });
2478 }
2479
2480 #[gpui::test]
2481 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
2482 let dir = temp_tree(json!({
2483 "root": {
2484 "dir1": {},
2485 "dir2": {
2486 "dir3": {}
2487 }
2488 }
2489 }));
2490
2491 let project = build_project(Arc::new(RealFs), &mut cx);
2492 let (tree, _) = project
2493 .update(&mut cx, |project, cx| {
2494 project.find_or_create_local_worktree(&dir.path(), false, cx)
2495 })
2496 .await
2497 .unwrap();
2498
2499 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2500 .await;
2501
2502 let cancel_flag = Default::default();
2503 let results = project
2504 .read_with(&cx, |project, cx| {
2505 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
2506 })
2507 .await;
2508
2509 assert!(results.is_empty());
2510 }
2511
2512 #[gpui::test]
2513 async fn test_definition(mut cx: gpui::TestAppContext) {
2514 let (language_server_config, mut fake_server) =
2515 LanguageServerConfig::fake(cx.background()).await;
2516
2517 let mut languages = LanguageRegistry::new();
2518 languages.add(Arc::new(Language::new(
2519 LanguageConfig {
2520 name: "Rust".to_string(),
2521 path_suffixes: vec!["rs".to_string()],
2522 language_server: Some(language_server_config),
2523 ..Default::default()
2524 },
2525 Some(tree_sitter_rust::language()),
2526 )));
2527
2528 let dir = temp_tree(json!({
2529 "a.rs": "const fn a() { A }",
2530 "b.rs": "const y: i32 = crate::a()",
2531 }));
2532
2533 let http_client = FakeHttpClient::with_404_response();
2534 let client = Client::new(http_client.clone());
2535 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
2536 let project = cx.update(|cx| {
2537 Project::local(
2538 client,
2539 user_store,
2540 Arc::new(languages),
2541 Arc::new(RealFs),
2542 cx,
2543 )
2544 });
2545
2546 let (tree, _) = project
2547 .update(&mut cx, |project, cx| {
2548 project.find_or_create_local_worktree(dir.path().join("b.rs"), false, cx)
2549 })
2550 .await
2551 .unwrap();
2552 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
2553 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2554 .await;
2555
2556 // Cause worktree to start the fake language server
2557 let buffer = project
2558 .update(&mut cx, |project, cx| {
2559 project.open_buffer(
2560 ProjectPath {
2561 worktree_id,
2562 path: Path::new("").into(),
2563 },
2564 cx,
2565 )
2566 })
2567 .await
2568 .unwrap();
2569 let definitions =
2570 project.update(&mut cx, |project, cx| project.definition(&buffer, 22, cx));
2571 let (request_id, request) = fake_server
2572 .receive_request::<lsp::request::GotoDefinition>()
2573 .await;
2574 let request_params = request.text_document_position_params;
2575 assert_eq!(
2576 request_params.text_document.uri.to_file_path().unwrap(),
2577 dir.path().join("b.rs")
2578 );
2579 assert_eq!(request_params.position, lsp::Position::new(0, 22));
2580
2581 fake_server
2582 .respond(
2583 request_id,
2584 Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
2585 lsp::Url::from_file_path(dir.path().join("a.rs")).unwrap(),
2586 lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
2587 ))),
2588 )
2589 .await;
2590 let mut definitions = definitions.await.unwrap();
2591 assert_eq!(definitions.len(), 1);
2592 let definition = definitions.pop().unwrap();
2593 cx.update(|cx| {
2594 let target_buffer = definition.target_buffer.read(cx);
2595 assert_eq!(
2596 target_buffer
2597 .file()
2598 .unwrap()
2599 .as_local()
2600 .unwrap()
2601 .abs_path(cx),
2602 dir.path().join("a.rs")
2603 );
2604 assert_eq!(definition.target_range.to_offset(target_buffer), 9..10);
2605 assert_eq!(
2606 list_worktrees(&project, cx),
2607 [
2608 (dir.path().join("b.rs"), false),
2609 (dir.path().join("a.rs"), true)
2610 ]
2611 );
2612
2613 drop(definition);
2614 });
2615 cx.read(|cx| {
2616 assert_eq!(
2617 list_worktrees(&project, cx),
2618 [(dir.path().join("b.rs"), false)]
2619 );
2620 });
2621
2622 fn list_worktrees(project: &ModelHandle<Project>, cx: &AppContext) -> Vec<(PathBuf, bool)> {
2623 project
2624 .read(cx)
2625 .worktrees(cx)
2626 .map(|worktree| {
2627 let worktree = worktree.read(cx);
2628 (
2629 worktree.as_local().unwrap().abs_path().to_path_buf(),
2630 worktree.is_weak(),
2631 )
2632 })
2633 .collect::<Vec<_>>()
2634 }
2635 }
2636
2637 #[gpui::test]
2638 async fn test_save_file(mut cx: gpui::TestAppContext) {
2639 let fs = Arc::new(FakeFs::new(cx.background()));
2640 fs.insert_tree(
2641 "/dir",
2642 json!({
2643 "file1": "the old contents",
2644 }),
2645 )
2646 .await;
2647
2648 let project = build_project(fs.clone(), &mut cx);
2649 let worktree_id = project
2650 .update(&mut cx, |p, cx| {
2651 p.find_or_create_local_worktree("/dir", false, cx)
2652 })
2653 .await
2654 .unwrap()
2655 .0
2656 .read_with(&cx, |tree, _| tree.id());
2657
2658 let buffer = project
2659 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
2660 .await
2661 .unwrap();
2662 buffer
2663 .update(&mut cx, |buffer, cx| {
2664 assert_eq!(buffer.text(), "the old contents");
2665 buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
2666 buffer.save(cx)
2667 })
2668 .await
2669 .unwrap();
2670
2671 let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2672 assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
2673 }
2674
2675 #[gpui::test]
2676 async fn test_save_in_single_file_worktree(mut cx: gpui::TestAppContext) {
2677 let fs = Arc::new(FakeFs::new(cx.background()));
2678 fs.insert_tree(
2679 "/dir",
2680 json!({
2681 "file1": "the old contents",
2682 }),
2683 )
2684 .await;
2685
2686 let project = build_project(fs.clone(), &mut cx);
2687 let worktree_id = project
2688 .update(&mut cx, |p, cx| {
2689 p.find_or_create_local_worktree("/dir/file1", false, cx)
2690 })
2691 .await
2692 .unwrap()
2693 .0
2694 .read_with(&cx, |tree, _| tree.id());
2695
2696 let buffer = project
2697 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, ""), cx))
2698 .await
2699 .unwrap();
2700 buffer
2701 .update(&mut cx, |buffer, cx| {
2702 buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
2703 buffer.save(cx)
2704 })
2705 .await
2706 .unwrap();
2707
2708 let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2709 assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
2710 }
2711
2712 #[gpui::test(retries = 5)]
2713 async fn test_rescan_and_remote_updates(mut cx: gpui::TestAppContext) {
2714 let dir = temp_tree(json!({
2715 "a": {
2716 "file1": "",
2717 "file2": "",
2718 "file3": "",
2719 },
2720 "b": {
2721 "c": {
2722 "file4": "",
2723 "file5": "",
2724 }
2725 }
2726 }));
2727
2728 let project = build_project(Arc::new(RealFs), &mut cx);
2729 let rpc = project.read_with(&cx, |p, _| p.client.clone());
2730
2731 let (tree, _) = project
2732 .update(&mut cx, |p, cx| {
2733 p.find_or_create_local_worktree(dir.path(), false, cx)
2734 })
2735 .await
2736 .unwrap();
2737 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
2738
2739 let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
2740 let buffer = project.update(cx, |p, cx| p.open_buffer((worktree_id, path), cx));
2741 async move { buffer.await.unwrap() }
2742 };
2743 let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
2744 tree.read_with(cx, |tree, _| {
2745 tree.entry_for_path(path)
2746 .expect(&format!("no entry for path {}", path))
2747 .id
2748 })
2749 };
2750
2751 let buffer2 = buffer_for_path("a/file2", &mut cx).await;
2752 let buffer3 = buffer_for_path("a/file3", &mut cx).await;
2753 let buffer4 = buffer_for_path("b/c/file4", &mut cx).await;
2754 let buffer5 = buffer_for_path("b/c/file5", &mut cx).await;
2755
2756 let file2_id = id_for_path("a/file2", &cx);
2757 let file3_id = id_for_path("a/file3", &cx);
2758 let file4_id = id_for_path("b/c/file4", &cx);
2759
2760 // Wait for the initial scan.
2761 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2762 .await;
2763
2764 // Create a remote copy of this worktree.
2765 let initial_snapshot = tree.read_with(&cx, |tree, _| tree.snapshot());
2766 let (remote, load_task) = cx.update(|cx| {
2767 Worktree::remote(
2768 1,
2769 1,
2770 initial_snapshot.to_proto(&Default::default(), Default::default()),
2771 rpc.clone(),
2772 cx,
2773 )
2774 });
2775 load_task.await;
2776
2777 cx.read(|cx| {
2778 assert!(!buffer2.read(cx).is_dirty());
2779 assert!(!buffer3.read(cx).is_dirty());
2780 assert!(!buffer4.read(cx).is_dirty());
2781 assert!(!buffer5.read(cx).is_dirty());
2782 });
2783
2784 // Rename and delete files and directories.
2785 tree.flush_fs_events(&cx).await;
2786 std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
2787 std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
2788 std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
2789 std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
2790 tree.flush_fs_events(&cx).await;
2791
2792 let expected_paths = vec![
2793 "a",
2794 "a/file1",
2795 "a/file2.new",
2796 "b",
2797 "d",
2798 "d/file3",
2799 "d/file4",
2800 ];
2801
2802 cx.read(|app| {
2803 assert_eq!(
2804 tree.read(app)
2805 .paths()
2806 .map(|p| p.to_str().unwrap())
2807 .collect::<Vec<_>>(),
2808 expected_paths
2809 );
2810
2811 assert_eq!(id_for_path("a/file2.new", &cx), file2_id);
2812 assert_eq!(id_for_path("d/file3", &cx), file3_id);
2813 assert_eq!(id_for_path("d/file4", &cx), file4_id);
2814
2815 assert_eq!(
2816 buffer2.read(app).file().unwrap().path().as_ref(),
2817 Path::new("a/file2.new")
2818 );
2819 assert_eq!(
2820 buffer3.read(app).file().unwrap().path().as_ref(),
2821 Path::new("d/file3")
2822 );
2823 assert_eq!(
2824 buffer4.read(app).file().unwrap().path().as_ref(),
2825 Path::new("d/file4")
2826 );
2827 assert_eq!(
2828 buffer5.read(app).file().unwrap().path().as_ref(),
2829 Path::new("b/c/file5")
2830 );
2831
2832 assert!(!buffer2.read(app).file().unwrap().is_deleted());
2833 assert!(!buffer3.read(app).file().unwrap().is_deleted());
2834 assert!(!buffer4.read(app).file().unwrap().is_deleted());
2835 assert!(buffer5.read(app).file().unwrap().is_deleted());
2836 });
2837
2838 // Update the remote worktree. Check that it becomes consistent with the
2839 // local worktree.
2840 remote.update(&mut cx, |remote, cx| {
2841 let update_message =
2842 tree.read(cx)
2843 .snapshot()
2844 .build_update(&initial_snapshot, 1, 1, true);
2845 remote
2846 .as_remote_mut()
2847 .unwrap()
2848 .snapshot
2849 .apply_remote_update(update_message)
2850 .unwrap();
2851
2852 assert_eq!(
2853 remote
2854 .paths()
2855 .map(|p| p.to_str().unwrap())
2856 .collect::<Vec<_>>(),
2857 expected_paths
2858 );
2859 });
2860 }
2861
2862 #[gpui::test]
2863 async fn test_buffer_deduping(mut cx: gpui::TestAppContext) {
2864 let fs = Arc::new(FakeFs::new(cx.background()));
2865 fs.insert_tree(
2866 "/the-dir",
2867 json!({
2868 "a.txt": "a-contents",
2869 "b.txt": "b-contents",
2870 }),
2871 )
2872 .await;
2873
2874 let project = build_project(fs.clone(), &mut cx);
2875 let worktree_id = project
2876 .update(&mut cx, |p, cx| {
2877 p.find_or_create_local_worktree("/the-dir", false, cx)
2878 })
2879 .await
2880 .unwrap()
2881 .0
2882 .read_with(&cx, |tree, _| tree.id());
2883
2884 // Spawn multiple tasks to open paths, repeating some paths.
2885 let (buffer_a_1, buffer_b, buffer_a_2) = project.update(&mut cx, |p, cx| {
2886 (
2887 p.open_buffer((worktree_id, "a.txt"), cx),
2888 p.open_buffer((worktree_id, "b.txt"), cx),
2889 p.open_buffer((worktree_id, "a.txt"), cx),
2890 )
2891 });
2892
2893 let buffer_a_1 = buffer_a_1.await.unwrap();
2894 let buffer_a_2 = buffer_a_2.await.unwrap();
2895 let buffer_b = buffer_b.await.unwrap();
2896 assert_eq!(buffer_a_1.read_with(&cx, |b, _| b.text()), "a-contents");
2897 assert_eq!(buffer_b.read_with(&cx, |b, _| b.text()), "b-contents");
2898
2899 // There is only one buffer per path.
2900 let buffer_a_id = buffer_a_1.id();
2901 assert_eq!(buffer_a_2.id(), buffer_a_id);
2902
2903 // Open the same path again while it is still open.
2904 drop(buffer_a_1);
2905 let buffer_a_3 = project
2906 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2907 .await
2908 .unwrap();
2909
2910 // There's still only one buffer per path.
2911 assert_eq!(buffer_a_3.id(), buffer_a_id);
2912 }
2913
2914 #[gpui::test]
2915 async fn test_buffer_is_dirty(mut cx: gpui::TestAppContext) {
2916 use std::fs;
2917
2918 let dir = temp_tree(json!({
2919 "file1": "abc",
2920 "file2": "def",
2921 "file3": "ghi",
2922 }));
2923
2924 let project = build_project(Arc::new(RealFs), &mut cx);
2925 let (worktree, _) = project
2926 .update(&mut cx, |p, cx| {
2927 p.find_or_create_local_worktree(dir.path(), false, cx)
2928 })
2929 .await
2930 .unwrap();
2931 let worktree_id = worktree.read_with(&cx, |worktree, _| worktree.id());
2932
2933 worktree.flush_fs_events(&cx).await;
2934 worktree
2935 .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete())
2936 .await;
2937
2938 let buffer1 = project
2939 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
2940 .await
2941 .unwrap();
2942 let events = Rc::new(RefCell::new(Vec::new()));
2943
2944 // initially, the buffer isn't dirty.
2945 buffer1.update(&mut cx, |buffer, cx| {
2946 cx.subscribe(&buffer1, {
2947 let events = events.clone();
2948 move |_, _, event, _| events.borrow_mut().push(event.clone())
2949 })
2950 .detach();
2951
2952 assert!(!buffer.is_dirty());
2953 assert!(events.borrow().is_empty());
2954
2955 buffer.edit(vec![1..2], "", cx);
2956 });
2957
2958 // after the first edit, the buffer is dirty, and emits a dirtied event.
2959 buffer1.update(&mut cx, |buffer, cx| {
2960 assert!(buffer.text() == "ac");
2961 assert!(buffer.is_dirty());
2962 assert_eq!(
2963 *events.borrow(),
2964 &[language::Event::Edited, language::Event::Dirtied]
2965 );
2966 events.borrow_mut().clear();
2967 buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
2968 });
2969
2970 // after saving, the buffer is not dirty, and emits a saved event.
2971 buffer1.update(&mut cx, |buffer, cx| {
2972 assert!(!buffer.is_dirty());
2973 assert_eq!(*events.borrow(), &[language::Event::Saved]);
2974 events.borrow_mut().clear();
2975
2976 buffer.edit(vec![1..1], "B", cx);
2977 buffer.edit(vec![2..2], "D", cx);
2978 });
2979
2980 // after editing again, the buffer is dirty, and emits another dirty event.
2981 buffer1.update(&mut cx, |buffer, cx| {
2982 assert!(buffer.text() == "aBDc");
2983 assert!(buffer.is_dirty());
2984 assert_eq!(
2985 *events.borrow(),
2986 &[
2987 language::Event::Edited,
2988 language::Event::Dirtied,
2989 language::Event::Edited,
2990 ],
2991 );
2992 events.borrow_mut().clear();
2993
2994 // TODO - currently, after restoring the buffer to its
2995 // previously-saved state, the is still considered dirty.
2996 buffer.edit([1..3], "", cx);
2997 assert!(buffer.text() == "ac");
2998 assert!(buffer.is_dirty());
2999 });
3000
3001 assert_eq!(*events.borrow(), &[language::Event::Edited]);
3002
3003 // When a file is deleted, the buffer is considered dirty.
3004 let events = Rc::new(RefCell::new(Vec::new()));
3005 let buffer2 = project
3006 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file2"), cx))
3007 .await
3008 .unwrap();
3009 buffer2.update(&mut cx, |_, cx| {
3010 cx.subscribe(&buffer2, {
3011 let events = events.clone();
3012 move |_, _, event, _| events.borrow_mut().push(event.clone())
3013 })
3014 .detach();
3015 });
3016
3017 fs::remove_file(dir.path().join("file2")).unwrap();
3018 buffer2.condition(&cx, |b, _| b.is_dirty()).await;
3019 assert_eq!(
3020 *events.borrow(),
3021 &[language::Event::Dirtied, language::Event::FileHandleChanged]
3022 );
3023
3024 // When a file is already dirty when deleted, we don't emit a Dirtied event.
3025 let events = Rc::new(RefCell::new(Vec::new()));
3026 let buffer3 = project
3027 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file3"), cx))
3028 .await
3029 .unwrap();
3030 buffer3.update(&mut cx, |_, cx| {
3031 cx.subscribe(&buffer3, {
3032 let events = events.clone();
3033 move |_, _, event, _| events.borrow_mut().push(event.clone())
3034 })
3035 .detach();
3036 });
3037
3038 worktree.flush_fs_events(&cx).await;
3039 buffer3.update(&mut cx, |buffer, cx| {
3040 buffer.edit(Some(0..0), "x", cx);
3041 });
3042 events.borrow_mut().clear();
3043 fs::remove_file(dir.path().join("file3")).unwrap();
3044 buffer3
3045 .condition(&cx, |_, _| !events.borrow().is_empty())
3046 .await;
3047 assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]);
3048 cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
3049 }
3050
3051 #[gpui::test]
3052 async fn test_buffer_file_changes_on_disk(mut cx: gpui::TestAppContext) {
3053 use std::fs;
3054
3055 let initial_contents = "aaa\nbbbbb\nc\n";
3056 let dir = temp_tree(json!({ "the-file": initial_contents }));
3057
3058 let project = build_project(Arc::new(RealFs), &mut cx);
3059 let (worktree, _) = project
3060 .update(&mut cx, |p, cx| {
3061 p.find_or_create_local_worktree(dir.path(), false, cx)
3062 })
3063 .await
3064 .unwrap();
3065 let worktree_id = worktree.read_with(&cx, |tree, _| tree.id());
3066
3067 worktree
3068 .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete())
3069 .await;
3070
3071 let abs_path = dir.path().join("the-file");
3072 let buffer = project
3073 .update(&mut cx, |p, cx| {
3074 p.open_buffer((worktree_id, "the-file"), cx)
3075 })
3076 .await
3077 .unwrap();
3078
3079 // TODO
3080 // Add a cursor on each row.
3081 // let selection_set_id = buffer.update(&mut cx, |buffer, cx| {
3082 // assert!(!buffer.is_dirty());
3083 // buffer.add_selection_set(
3084 // &(0..3)
3085 // .map(|row| Selection {
3086 // id: row as usize,
3087 // start: Point::new(row, 1),
3088 // end: Point::new(row, 1),
3089 // reversed: false,
3090 // goal: SelectionGoal::None,
3091 // })
3092 // .collect::<Vec<_>>(),
3093 // cx,
3094 // )
3095 // });
3096
3097 // Change the file on disk, adding two new lines of text, and removing
3098 // one line.
3099 buffer.read_with(&cx, |buffer, _| {
3100 assert!(!buffer.is_dirty());
3101 assert!(!buffer.has_conflict());
3102 });
3103 let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
3104 fs::write(&abs_path, new_contents).unwrap();
3105
3106 // Because the buffer was not modified, it is reloaded from disk. Its
3107 // contents are edited according to the diff between the old and new
3108 // file contents.
3109 buffer
3110 .condition(&cx, |buffer, _| buffer.text() == new_contents)
3111 .await;
3112
3113 buffer.update(&mut cx, |buffer, _| {
3114 assert_eq!(buffer.text(), new_contents);
3115 assert!(!buffer.is_dirty());
3116 assert!(!buffer.has_conflict());
3117
3118 // TODO
3119 // let cursor_positions = buffer
3120 // .selection_set(selection_set_id)
3121 // .unwrap()
3122 // .selections::<Point>(&*buffer)
3123 // .map(|selection| {
3124 // assert_eq!(selection.start, selection.end);
3125 // selection.start
3126 // })
3127 // .collect::<Vec<_>>();
3128 // assert_eq!(
3129 // cursor_positions,
3130 // [Point::new(1, 1), Point::new(3, 1), Point::new(4, 0)]
3131 // );
3132 });
3133
3134 // Modify the buffer
3135 buffer.update(&mut cx, |buffer, cx| {
3136 buffer.edit(vec![0..0], " ", cx);
3137 assert!(buffer.is_dirty());
3138 assert!(!buffer.has_conflict());
3139 });
3140
3141 // Change the file on disk again, adding blank lines to the beginning.
3142 fs::write(&abs_path, "\n\n\nAAAA\naaa\nBB\nbbbbb\n").unwrap();
3143
3144 // Because the buffer is modified, it doesn't reload from disk, but is
3145 // marked as having a conflict.
3146 buffer
3147 .condition(&cx, |buffer, _| buffer.has_conflict())
3148 .await;
3149 }
3150
3151 #[gpui::test]
3152 async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
3153 let fs = Arc::new(FakeFs::new(cx.background()));
3154 fs.insert_tree(
3155 "/the-dir",
3156 json!({
3157 "a.rs": "
3158 fn foo(mut v: Vec<usize>) {
3159 for x in &v {
3160 v.push(1);
3161 }
3162 }
3163 "
3164 .unindent(),
3165 }),
3166 )
3167 .await;
3168
3169 let project = build_project(fs.clone(), &mut cx);
3170 let (worktree, _) = project
3171 .update(&mut cx, |p, cx| {
3172 p.find_or_create_local_worktree("/the-dir", false, cx)
3173 })
3174 .await
3175 .unwrap();
3176 let worktree_id = worktree.read_with(&cx, |tree, _| tree.id());
3177
3178 let buffer = project
3179 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
3180 .await
3181 .unwrap();
3182
3183 let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
3184 let message = lsp::PublishDiagnosticsParams {
3185 uri: buffer_uri.clone(),
3186 diagnostics: vec![
3187 lsp::Diagnostic {
3188 range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
3189 severity: Some(DiagnosticSeverity::WARNING),
3190 message: "error 1".to_string(),
3191 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3192 location: lsp::Location {
3193 uri: buffer_uri.clone(),
3194 range: lsp::Range::new(
3195 lsp::Position::new(1, 8),
3196 lsp::Position::new(1, 9),
3197 ),
3198 },
3199 message: "error 1 hint 1".to_string(),
3200 }]),
3201 ..Default::default()
3202 },
3203 lsp::Diagnostic {
3204 range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
3205 severity: Some(DiagnosticSeverity::HINT),
3206 message: "error 1 hint 1".to_string(),
3207 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3208 location: lsp::Location {
3209 uri: buffer_uri.clone(),
3210 range: lsp::Range::new(
3211 lsp::Position::new(1, 8),
3212 lsp::Position::new(1, 9),
3213 ),
3214 },
3215 message: "original diagnostic".to_string(),
3216 }]),
3217 ..Default::default()
3218 },
3219 lsp::Diagnostic {
3220 range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
3221 severity: Some(DiagnosticSeverity::ERROR),
3222 message: "error 2".to_string(),
3223 related_information: Some(vec![
3224 lsp::DiagnosticRelatedInformation {
3225 location: lsp::Location {
3226 uri: buffer_uri.clone(),
3227 range: lsp::Range::new(
3228 lsp::Position::new(1, 13),
3229 lsp::Position::new(1, 15),
3230 ),
3231 },
3232 message: "error 2 hint 1".to_string(),
3233 },
3234 lsp::DiagnosticRelatedInformation {
3235 location: lsp::Location {
3236 uri: buffer_uri.clone(),
3237 range: lsp::Range::new(
3238 lsp::Position::new(1, 13),
3239 lsp::Position::new(1, 15),
3240 ),
3241 },
3242 message: "error 2 hint 2".to_string(),
3243 },
3244 ]),
3245 ..Default::default()
3246 },
3247 lsp::Diagnostic {
3248 range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
3249 severity: Some(DiagnosticSeverity::HINT),
3250 message: "error 2 hint 1".to_string(),
3251 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3252 location: lsp::Location {
3253 uri: buffer_uri.clone(),
3254 range: lsp::Range::new(
3255 lsp::Position::new(2, 8),
3256 lsp::Position::new(2, 17),
3257 ),
3258 },
3259 message: "original diagnostic".to_string(),
3260 }]),
3261 ..Default::default()
3262 },
3263 lsp::Diagnostic {
3264 range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
3265 severity: Some(DiagnosticSeverity::HINT),
3266 message: "error 2 hint 2".to_string(),
3267 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3268 location: lsp::Location {
3269 uri: buffer_uri.clone(),
3270 range: lsp::Range::new(
3271 lsp::Position::new(2, 8),
3272 lsp::Position::new(2, 17),
3273 ),
3274 },
3275 message: "original diagnostic".to_string(),
3276 }]),
3277 ..Default::default()
3278 },
3279 ],
3280 version: None,
3281 };
3282
3283 project
3284 .update(&mut cx, |p, cx| {
3285 p.update_diagnostics(message, &Default::default(), cx)
3286 })
3287 .unwrap();
3288 let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot());
3289
3290 assert_eq!(
3291 buffer
3292 .diagnostics_in_range::<_, Point>(0..buffer.len())
3293 .collect::<Vec<_>>(),
3294 &[
3295 DiagnosticEntry {
3296 range: Point::new(1, 8)..Point::new(1, 9),
3297 diagnostic: Diagnostic {
3298 severity: DiagnosticSeverity::WARNING,
3299 message: "error 1".to_string(),
3300 group_id: 0,
3301 is_primary: true,
3302 ..Default::default()
3303 }
3304 },
3305 DiagnosticEntry {
3306 range: Point::new(1, 8)..Point::new(1, 9),
3307 diagnostic: Diagnostic {
3308 severity: DiagnosticSeverity::HINT,
3309 message: "error 1 hint 1".to_string(),
3310 group_id: 0,
3311 is_primary: false,
3312 ..Default::default()
3313 }
3314 },
3315 DiagnosticEntry {
3316 range: Point::new(1, 13)..Point::new(1, 15),
3317 diagnostic: Diagnostic {
3318 severity: DiagnosticSeverity::HINT,
3319 message: "error 2 hint 1".to_string(),
3320 group_id: 1,
3321 is_primary: false,
3322 ..Default::default()
3323 }
3324 },
3325 DiagnosticEntry {
3326 range: Point::new(1, 13)..Point::new(1, 15),
3327 diagnostic: Diagnostic {
3328 severity: DiagnosticSeverity::HINT,
3329 message: "error 2 hint 2".to_string(),
3330 group_id: 1,
3331 is_primary: false,
3332 ..Default::default()
3333 }
3334 },
3335 DiagnosticEntry {
3336 range: Point::new(2, 8)..Point::new(2, 17),
3337 diagnostic: Diagnostic {
3338 severity: DiagnosticSeverity::ERROR,
3339 message: "error 2".to_string(),
3340 group_id: 1,
3341 is_primary: true,
3342 ..Default::default()
3343 }
3344 }
3345 ]
3346 );
3347
3348 assert_eq!(
3349 buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
3350 &[
3351 DiagnosticEntry {
3352 range: Point::new(1, 8)..Point::new(1, 9),
3353 diagnostic: Diagnostic {
3354 severity: DiagnosticSeverity::WARNING,
3355 message: "error 1".to_string(),
3356 group_id: 0,
3357 is_primary: true,
3358 ..Default::default()
3359 }
3360 },
3361 DiagnosticEntry {
3362 range: Point::new(1, 8)..Point::new(1, 9),
3363 diagnostic: Diagnostic {
3364 severity: DiagnosticSeverity::HINT,
3365 message: "error 1 hint 1".to_string(),
3366 group_id: 0,
3367 is_primary: false,
3368 ..Default::default()
3369 }
3370 },
3371 ]
3372 );
3373 assert_eq!(
3374 buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
3375 &[
3376 DiagnosticEntry {
3377 range: Point::new(1, 13)..Point::new(1, 15),
3378 diagnostic: Diagnostic {
3379 severity: DiagnosticSeverity::HINT,
3380 message: "error 2 hint 1".to_string(),
3381 group_id: 1,
3382 is_primary: false,
3383 ..Default::default()
3384 }
3385 },
3386 DiagnosticEntry {
3387 range: Point::new(1, 13)..Point::new(1, 15),
3388 diagnostic: Diagnostic {
3389 severity: DiagnosticSeverity::HINT,
3390 message: "error 2 hint 2".to_string(),
3391 group_id: 1,
3392 is_primary: false,
3393 ..Default::default()
3394 }
3395 },
3396 DiagnosticEntry {
3397 range: Point::new(2, 8)..Point::new(2, 17),
3398 diagnostic: Diagnostic {
3399 severity: DiagnosticSeverity::ERROR,
3400 message: "error 2".to_string(),
3401 group_id: 1,
3402 is_primary: true,
3403 ..Default::default()
3404 }
3405 }
3406 ]
3407 );
3408 }
3409
3410 fn build_project(fs: Arc<dyn Fs>, cx: &mut TestAppContext) -> ModelHandle<Project> {
3411 let languages = Arc::new(LanguageRegistry::new());
3412 let http_client = FakeHttpClient::with_404_response();
3413 let client = client::Client::new(http_client.clone());
3414 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
3415 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
3416 }
3417}