1pub mod fs;
2mod ignore;
3mod lsp_command;
4pub mod worktree;
5
6use anyhow::{anyhow, Context, Result};
7use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
8use clock::ReplicaId;
9use collections::{hash_map, HashMap, HashSet};
10use futures::{future::Shared, Future, FutureExt};
11use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
12use gpui::{
13 AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
14 UpgradeModelHandle, WeakModelHandle,
15};
16use language::{
17 range_from_lsp, Anchor, AnchorRangeExt, Bias, Buffer, CodeAction, CodeLabel, Completion,
18 Diagnostic, DiagnosticEntry, File as _, Language, LanguageRegistry, Operation, PointUtf16,
19 ToLspPosition, ToOffset, ToPointUtf16, Transaction,
20};
21use lsp::{DiagnosticSeverity, LanguageServer};
22use lsp_command::*;
23use postage::{broadcast, prelude::Stream, sink::Sink, watch};
24use rand::prelude::*;
25use sha2::{Digest, Sha256};
26use smol::block_on;
27use std::{
28 convert::TryInto,
29 hash::Hash,
30 mem,
31 ops::Range,
32 path::{Component, Path, PathBuf},
33 sync::{atomic::AtomicBool, Arc},
34 time::Instant,
35};
36use util::{post_inc, ResultExt, TryFutureExt as _};
37
38pub use fs::*;
39pub use worktree::*;
40
41pub struct Project {
42 worktrees: Vec<WorktreeHandle>,
43 active_entry: Option<ProjectEntry>,
44 languages: Arc<LanguageRegistry>,
45 language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
46 started_language_servers:
47 HashMap<(WorktreeId, String), Shared<Task<Option<Arc<LanguageServer>>>>>,
48 client: Arc<client::Client>,
49 user_store: ModelHandle<UserStore>,
50 fs: Arc<dyn Fs>,
51 client_state: ProjectClientState,
52 collaborators: HashMap<PeerId, Collaborator>,
53 subscriptions: Vec<client::Subscription>,
54 language_servers_with_diagnostics_running: isize,
55 open_buffers: HashMap<u64, OpenBuffer>,
56 opened_buffer: broadcast::Sender<()>,
57 loading_buffers: HashMap<
58 ProjectPath,
59 postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
60 >,
61 shared_buffers: HashMap<PeerId, HashMap<u64, ModelHandle<Buffer>>>,
62 nonce: u128,
63}
64
65enum OpenBuffer {
66 Loaded(WeakModelHandle<Buffer>),
67 Loading(Vec<Operation>),
68}
69
70enum WorktreeHandle {
71 Strong(ModelHandle<Worktree>),
72 Weak(WeakModelHandle<Worktree>),
73}
74
75enum ProjectClientState {
76 Local {
77 is_shared: bool,
78 remote_id_tx: watch::Sender<Option<u64>>,
79 remote_id_rx: watch::Receiver<Option<u64>>,
80 _maintain_remote_id_task: Task<Option<()>>,
81 },
82 Remote {
83 sharing_has_stopped: bool,
84 remote_id: u64,
85 replica_id: ReplicaId,
86 },
87}
88
89#[derive(Clone, Debug)]
90pub struct Collaborator {
91 pub user: Arc<User>,
92 pub peer_id: PeerId,
93 pub replica_id: ReplicaId,
94}
95
96#[derive(Clone, Debug, PartialEq)]
97pub enum Event {
98 ActiveEntryChanged(Option<ProjectEntry>),
99 WorktreeRemoved(WorktreeId),
100 DiskBasedDiagnosticsStarted,
101 DiskBasedDiagnosticsUpdated,
102 DiskBasedDiagnosticsFinished,
103 DiagnosticsUpdated(ProjectPath),
104}
105
106#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
107pub struct ProjectPath {
108 pub worktree_id: WorktreeId,
109 pub path: Arc<Path>,
110}
111
112#[derive(Clone, Debug, Default, PartialEq)]
113pub struct DiagnosticSummary {
114 pub error_count: usize,
115 pub warning_count: usize,
116 pub info_count: usize,
117 pub hint_count: usize,
118}
119
120#[derive(Debug)]
121pub struct Definition {
122 pub target_buffer: ModelHandle<Buffer>,
123 pub target_range: Range<language::Anchor>,
124}
125
126#[derive(Clone, Debug)]
127pub struct Symbol {
128 pub source_worktree_id: WorktreeId,
129 pub worktree_id: WorktreeId,
130 pub language_name: String,
131 pub path: PathBuf,
132 pub label: CodeLabel,
133 pub name: String,
134 pub kind: lsp::SymbolKind,
135 pub range: Range<PointUtf16>,
136 pub signature: [u8; 32],
137}
138
139#[derive(Default)]
140pub struct ProjectTransaction(pub HashMap<ModelHandle<Buffer>, language::Transaction>);
141
142impl DiagnosticSummary {
143 fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
144 let mut this = Self {
145 error_count: 0,
146 warning_count: 0,
147 info_count: 0,
148 hint_count: 0,
149 };
150
151 for entry in diagnostics {
152 if entry.diagnostic.is_primary {
153 match entry.diagnostic.severity {
154 DiagnosticSeverity::ERROR => this.error_count += 1,
155 DiagnosticSeverity::WARNING => this.warning_count += 1,
156 DiagnosticSeverity::INFORMATION => this.info_count += 1,
157 DiagnosticSeverity::HINT => this.hint_count += 1,
158 _ => {}
159 }
160 }
161 }
162
163 this
164 }
165
166 pub fn to_proto(&self, path: Arc<Path>) -> proto::DiagnosticSummary {
167 proto::DiagnosticSummary {
168 path: path.to_string_lossy().to_string(),
169 error_count: self.error_count as u32,
170 warning_count: self.warning_count as u32,
171 info_count: self.info_count as u32,
172 hint_count: self.hint_count as u32,
173 }
174 }
175}
176
177#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
178pub struct ProjectEntry {
179 pub worktree_id: WorktreeId,
180 pub entry_id: usize,
181}
182
183impl Project {
184 pub fn init(client: &Arc<Client>) {
185 client.add_entity_message_handler(Self::handle_add_collaborator);
186 client.add_entity_message_handler(Self::handle_buffer_reloaded);
187 client.add_entity_message_handler(Self::handle_buffer_saved);
188 client.add_entity_message_handler(Self::handle_close_buffer);
189 client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updated);
190 client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updating);
191 client.add_entity_message_handler(Self::handle_remove_collaborator);
192 client.add_entity_message_handler(Self::handle_share_worktree);
193 client.add_entity_message_handler(Self::handle_unregister_worktree);
194 client.add_entity_message_handler(Self::handle_unshare_project);
195 client.add_entity_message_handler(Self::handle_update_buffer_file);
196 client.add_entity_message_handler(Self::handle_update_buffer);
197 client.add_entity_message_handler(Self::handle_update_diagnostic_summary);
198 client.add_entity_message_handler(Self::handle_update_worktree);
199 client.add_entity_request_handler(Self::handle_apply_additional_edits_for_completion);
200 client.add_entity_request_handler(Self::handle_apply_code_action);
201 client.add_entity_request_handler(Self::handle_format_buffers);
202 client.add_entity_request_handler(Self::handle_get_code_actions);
203 client.add_entity_request_handler(Self::handle_get_completions);
204 client.add_entity_request_handler(Self::handle_lsp_command::<GetDefinition>);
205 client.add_entity_request_handler(Self::handle_lsp_command::<PrepareRename>);
206 client.add_entity_request_handler(Self::handle_lsp_command::<PerformRename>);
207 client.add_entity_request_handler(Self::handle_get_project_symbols);
208 client.add_entity_request_handler(Self::handle_open_buffer_for_symbol);
209 client.add_entity_request_handler(Self::handle_open_buffer);
210 client.add_entity_request_handler(Self::handle_save_buffer);
211 }
212
213 pub fn local(
214 client: Arc<Client>,
215 user_store: ModelHandle<UserStore>,
216 languages: Arc<LanguageRegistry>,
217 fs: Arc<dyn Fs>,
218 cx: &mut MutableAppContext,
219 ) -> ModelHandle<Self> {
220 cx.add_model(|cx: &mut ModelContext<Self>| {
221 let (remote_id_tx, remote_id_rx) = watch::channel();
222 let _maintain_remote_id_task = cx.spawn_weak({
223 let rpc = client.clone();
224 move |this, mut cx| {
225 async move {
226 let mut status = rpc.status();
227 while let Some(status) = status.recv().await {
228 if let Some(this) = this.upgrade(&cx) {
229 let remote_id = if let client::Status::Connected { .. } = status {
230 let response = rpc.request(proto::RegisterProject {}).await?;
231 Some(response.project_id)
232 } else {
233 None
234 };
235
236 if let Some(project_id) = remote_id {
237 let mut registrations = Vec::new();
238 this.update(&mut cx, |this, cx| {
239 for worktree in this.worktrees(cx).collect::<Vec<_>>() {
240 registrations.push(worktree.update(
241 cx,
242 |worktree, cx| {
243 let worktree = worktree.as_local_mut().unwrap();
244 worktree.register(project_id, cx)
245 },
246 ));
247 }
248 });
249 for registration in registrations {
250 registration.await?;
251 }
252 }
253 this.update(&mut cx, |this, cx| this.set_remote_id(remote_id, cx));
254 }
255 }
256 Ok(())
257 }
258 .log_err()
259 }
260 });
261
262 Self {
263 worktrees: Default::default(),
264 collaborators: Default::default(),
265 open_buffers: Default::default(),
266 loading_buffers: Default::default(),
267 shared_buffers: Default::default(),
268 client_state: ProjectClientState::Local {
269 is_shared: false,
270 remote_id_tx,
271 remote_id_rx,
272 _maintain_remote_id_task,
273 },
274 opened_buffer: broadcast::channel(1).0,
275 subscriptions: Vec::new(),
276 active_entry: None,
277 languages,
278 client,
279 user_store,
280 fs,
281 language_servers_with_diagnostics_running: 0,
282 language_servers: Default::default(),
283 started_language_servers: Default::default(),
284 nonce: StdRng::from_entropy().gen(),
285 }
286 })
287 }
288
289 pub async fn remote(
290 remote_id: u64,
291 client: Arc<Client>,
292 user_store: ModelHandle<UserStore>,
293 languages: Arc<LanguageRegistry>,
294 fs: Arc<dyn Fs>,
295 cx: &mut AsyncAppContext,
296 ) -> Result<ModelHandle<Self>> {
297 client.authenticate_and_connect(&cx).await?;
298
299 let response = client
300 .request(proto::JoinProject {
301 project_id: remote_id,
302 })
303 .await?;
304
305 let replica_id = response.replica_id as ReplicaId;
306
307 let mut worktrees = Vec::new();
308 for worktree in response.worktrees {
309 let (worktree, load_task) = cx
310 .update(|cx| Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx));
311 worktrees.push(worktree);
312 load_task.detach();
313 }
314
315 let this = cx.add_model(|cx| {
316 let mut this = Self {
317 worktrees: Vec::new(),
318 open_buffers: Default::default(),
319 loading_buffers: Default::default(),
320 opened_buffer: broadcast::channel(1).0,
321 shared_buffers: Default::default(),
322 active_entry: None,
323 collaborators: Default::default(),
324 languages,
325 user_store: user_store.clone(),
326 fs,
327 subscriptions: vec![client.add_model_for_remote_entity(remote_id, cx)],
328 client,
329 client_state: ProjectClientState::Remote {
330 sharing_has_stopped: false,
331 remote_id,
332 replica_id,
333 },
334 language_servers_with_diagnostics_running: 0,
335 language_servers: Default::default(),
336 started_language_servers: Default::default(),
337 nonce: StdRng::from_entropy().gen(),
338 };
339 for worktree in worktrees {
340 this.add_worktree(&worktree, cx);
341 }
342 this
343 });
344
345 let user_ids = response
346 .collaborators
347 .iter()
348 .map(|peer| peer.user_id)
349 .collect();
350 user_store
351 .update(cx, |user_store, cx| user_store.load_users(user_ids, cx))
352 .await?;
353 let mut collaborators = HashMap::default();
354 for message in response.collaborators {
355 let collaborator = Collaborator::from_proto(message, &user_store, cx).await?;
356 collaborators.insert(collaborator.peer_id, collaborator);
357 }
358
359 this.update(cx, |this, _| {
360 this.collaborators = collaborators;
361 });
362
363 Ok(this)
364 }
365
366 #[cfg(any(test, feature = "test-support"))]
367 pub fn test(fs: Arc<dyn Fs>, cx: &mut gpui::TestAppContext) -> ModelHandle<Project> {
368 let languages = Arc::new(LanguageRegistry::new());
369 let http_client = client::test::FakeHttpClient::with_404_response();
370 let client = client::Client::new(http_client.clone());
371 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
372 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
373 }
374
375 #[cfg(any(test, feature = "test-support"))]
376 pub fn shared_buffer(&self, peer_id: PeerId, remote_id: u64) -> Option<ModelHandle<Buffer>> {
377 self.shared_buffers
378 .get(&peer_id)
379 .and_then(|buffers| buffers.get(&remote_id))
380 .cloned()
381 }
382
383 #[cfg(any(test, feature = "test-support"))]
384 pub fn has_buffered_operations(&self) -> bool {
385 self.open_buffers
386 .values()
387 .any(|buffer| matches!(buffer, OpenBuffer::Loading(_)))
388 }
389
390 #[cfg(any(test, feature = "test-support"))]
391 pub fn languages(&self) -> &Arc<LanguageRegistry> {
392 &self.languages
393 }
394
395 pub fn fs(&self) -> &Arc<dyn Fs> {
396 &self.fs
397 }
398
399 fn set_remote_id(&mut self, remote_id: Option<u64>, cx: &mut ModelContext<Self>) {
400 if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state {
401 *remote_id_tx.borrow_mut() = remote_id;
402 }
403
404 self.subscriptions.clear();
405 if let Some(remote_id) = remote_id {
406 self.subscriptions
407 .push(self.client.add_model_for_remote_entity(remote_id, cx));
408 }
409 }
410
411 pub fn remote_id(&self) -> Option<u64> {
412 match &self.client_state {
413 ProjectClientState::Local { remote_id_rx, .. } => *remote_id_rx.borrow(),
414 ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
415 }
416 }
417
418 pub fn next_remote_id(&self) -> impl Future<Output = u64> {
419 let mut id = None;
420 let mut watch = None;
421 match &self.client_state {
422 ProjectClientState::Local { remote_id_rx, .. } => watch = Some(remote_id_rx.clone()),
423 ProjectClientState::Remote { remote_id, .. } => id = Some(*remote_id),
424 }
425
426 async move {
427 if let Some(id) = id {
428 return id;
429 }
430 let mut watch = watch.unwrap();
431 loop {
432 let id = *watch.borrow();
433 if let Some(id) = id {
434 return id;
435 }
436 watch.recv().await;
437 }
438 }
439 }
440
441 pub fn replica_id(&self) -> ReplicaId {
442 match &self.client_state {
443 ProjectClientState::Local { .. } => 0,
444 ProjectClientState::Remote { replica_id, .. } => *replica_id,
445 }
446 }
447
448 pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
449 &self.collaborators
450 }
451
452 pub fn worktrees<'a>(
453 &'a self,
454 cx: &'a AppContext,
455 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
456 self.worktrees
457 .iter()
458 .filter_map(move |worktree| worktree.upgrade(cx))
459 }
460
461 pub fn strong_worktrees<'a>(
462 &'a self,
463 cx: &'a AppContext,
464 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
465 self.worktrees.iter().filter_map(|worktree| {
466 worktree.upgrade(cx).and_then(|worktree| {
467 if worktree.read(cx).is_weak() {
468 None
469 } else {
470 Some(worktree)
471 }
472 })
473 })
474 }
475
476 pub fn worktree_for_id(
477 &self,
478 id: WorktreeId,
479 cx: &AppContext,
480 ) -> Option<ModelHandle<Worktree>> {
481 self.worktrees(cx)
482 .find(|worktree| worktree.read(cx).id() == id)
483 }
484
485 pub fn share(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
486 let rpc = self.client.clone();
487 cx.spawn(|this, mut cx| async move {
488 let project_id = this.update(&mut cx, |this, _| {
489 if let ProjectClientState::Local {
490 is_shared,
491 remote_id_rx,
492 ..
493 } = &mut this.client_state
494 {
495 *is_shared = true;
496 remote_id_rx
497 .borrow()
498 .ok_or_else(|| anyhow!("no project id"))
499 } else {
500 Err(anyhow!("can't share a remote project"))
501 }
502 })?;
503
504 rpc.request(proto::ShareProject { project_id }).await?;
505 let mut tasks = Vec::new();
506 this.update(&mut cx, |this, cx| {
507 for worktree in this.worktrees(cx).collect::<Vec<_>>() {
508 worktree.update(cx, |worktree, cx| {
509 let worktree = worktree.as_local_mut().unwrap();
510 tasks.push(worktree.share(project_id, cx));
511 });
512 }
513 });
514 for task in tasks {
515 task.await?;
516 }
517 this.update(&mut cx, |_, cx| cx.notify());
518 Ok(())
519 })
520 }
521
522 pub fn unshare(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
523 let rpc = self.client.clone();
524 cx.spawn(|this, mut cx| async move {
525 let project_id = this.update(&mut cx, |this, _| {
526 if let ProjectClientState::Local {
527 is_shared,
528 remote_id_rx,
529 ..
530 } = &mut this.client_state
531 {
532 *is_shared = false;
533 remote_id_rx
534 .borrow()
535 .ok_or_else(|| anyhow!("no project id"))
536 } else {
537 Err(anyhow!("can't share a remote project"))
538 }
539 })?;
540
541 rpc.send(proto::UnshareProject { project_id })?;
542 this.update(&mut cx, |this, cx| {
543 this.collaborators.clear();
544 this.shared_buffers.clear();
545 for worktree in this.worktrees(cx).collect::<Vec<_>>() {
546 worktree.update(cx, |worktree, _| {
547 worktree.as_local_mut().unwrap().unshare();
548 });
549 }
550 cx.notify()
551 });
552 Ok(())
553 })
554 }
555
556 pub fn is_read_only(&self) -> bool {
557 match &self.client_state {
558 ProjectClientState::Local { .. } => false,
559 ProjectClientState::Remote {
560 sharing_has_stopped,
561 ..
562 } => *sharing_has_stopped,
563 }
564 }
565
566 pub fn is_local(&self) -> bool {
567 match &self.client_state {
568 ProjectClientState::Local { .. } => true,
569 ProjectClientState::Remote { .. } => false,
570 }
571 }
572
573 pub fn is_remote(&self) -> bool {
574 !self.is_local()
575 }
576
577 pub fn open_buffer(
578 &mut self,
579 path: impl Into<ProjectPath>,
580 cx: &mut ModelContext<Self>,
581 ) -> Task<Result<ModelHandle<Buffer>>> {
582 let project_path = path.into();
583 let worktree = if let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) {
584 worktree
585 } else {
586 return Task::ready(Err(anyhow!("no such worktree")));
587 };
588
589 // If there is already a buffer for the given path, then return it.
590 let existing_buffer = self.get_open_buffer(&project_path, cx);
591 if let Some(existing_buffer) = existing_buffer {
592 return Task::ready(Ok(existing_buffer));
593 }
594
595 let mut loading_watch = match self.loading_buffers.entry(project_path.clone()) {
596 // If the given path is already being loaded, then wait for that existing
597 // task to complete and return the same buffer.
598 hash_map::Entry::Occupied(e) => e.get().clone(),
599
600 // Otherwise, record the fact that this path is now being loaded.
601 hash_map::Entry::Vacant(entry) => {
602 let (mut tx, rx) = postage::watch::channel();
603 entry.insert(rx.clone());
604
605 let load_buffer = if worktree.read(cx).is_local() {
606 self.open_local_buffer(&project_path.path, &worktree, cx)
607 } else {
608 self.open_remote_buffer(&project_path.path, &worktree, cx)
609 };
610
611 cx.spawn(move |this, mut cx| async move {
612 let load_result = load_buffer.await;
613 *tx.borrow_mut() = Some(this.update(&mut cx, |this, _| {
614 // Record the fact that the buffer is no longer loading.
615 this.loading_buffers.remove(&project_path);
616 if this.loading_buffers.is_empty() {
617 this.open_buffers
618 .retain(|_, buffer| matches!(buffer, OpenBuffer::Loaded(_)))
619 }
620
621 let buffer = load_result.map_err(Arc::new)?;
622 Ok(buffer)
623 }));
624 })
625 .detach();
626 rx
627 }
628 };
629
630 cx.foreground().spawn(async move {
631 loop {
632 if let Some(result) = loading_watch.borrow().as_ref() {
633 match result {
634 Ok(buffer) => return Ok(buffer.clone()),
635 Err(error) => return Err(anyhow!("{}", error)),
636 }
637 }
638 loading_watch.recv().await;
639 }
640 })
641 }
642
643 fn open_local_buffer(
644 &mut self,
645 path: &Arc<Path>,
646 worktree: &ModelHandle<Worktree>,
647 cx: &mut ModelContext<Self>,
648 ) -> Task<Result<ModelHandle<Buffer>>> {
649 let load_buffer = worktree.update(cx, |worktree, cx| {
650 let worktree = worktree.as_local_mut().unwrap();
651 worktree.load_buffer(path, cx)
652 });
653 let worktree = worktree.downgrade();
654 cx.spawn(|this, mut cx| async move {
655 let buffer = load_buffer.await?;
656 let worktree = worktree
657 .upgrade(&cx)
658 .ok_or_else(|| anyhow!("worktree was removed"))?;
659 this.update(&mut cx, |this, cx| {
660 this.register_buffer(&buffer, Some(&worktree), cx)
661 })?;
662 Ok(buffer)
663 })
664 }
665
666 fn open_remote_buffer(
667 &mut self,
668 path: &Arc<Path>,
669 worktree: &ModelHandle<Worktree>,
670 cx: &mut ModelContext<Self>,
671 ) -> Task<Result<ModelHandle<Buffer>>> {
672 let rpc = self.client.clone();
673 let project_id = self.remote_id().unwrap();
674 let remote_worktree_id = worktree.read(cx).id();
675 let path = path.clone();
676 let path_string = path.to_string_lossy().to_string();
677 cx.spawn(|this, mut cx| async move {
678 let response = rpc
679 .request(proto::OpenBuffer {
680 project_id,
681 worktree_id: remote_worktree_id.to_proto(),
682 path: path_string,
683 })
684 .await?;
685 let buffer = response.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
686 this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
687 .await
688 })
689 }
690
691 fn open_local_buffer_via_lsp(
692 &mut self,
693 abs_path: lsp::Url,
694 lang_name: String,
695 lang_server: Arc<LanguageServer>,
696 cx: &mut ModelContext<Self>,
697 ) -> Task<Result<ModelHandle<Buffer>>> {
698 cx.spawn(|this, mut cx| async move {
699 let abs_path = abs_path
700 .to_file_path()
701 .map_err(|_| anyhow!("can't convert URI to path"))?;
702 let (worktree, relative_path) = if let Some(result) =
703 this.read_with(&cx, |this, cx| this.find_local_worktree(&abs_path, cx))
704 {
705 result
706 } else {
707 let worktree = this
708 .update(&mut cx, |this, cx| {
709 this.create_local_worktree(&abs_path, true, cx)
710 })
711 .await?;
712 this.update(&mut cx, |this, cx| {
713 this.language_servers
714 .insert((worktree.read(cx).id(), lang_name), lang_server);
715 });
716 (worktree, PathBuf::new())
717 };
718
719 let project_path = ProjectPath {
720 worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
721 path: relative_path.into(),
722 };
723 this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
724 .await
725 })
726 }
727
728 pub fn save_buffer_as(
729 &self,
730 buffer: ModelHandle<Buffer>,
731 abs_path: PathBuf,
732 cx: &mut ModelContext<Project>,
733 ) -> Task<Result<()>> {
734 let worktree_task = self.find_or_create_local_worktree(&abs_path, false, cx);
735 cx.spawn(|this, mut cx| async move {
736 let (worktree, path) = worktree_task.await?;
737 worktree
738 .update(&mut cx, |worktree, cx| {
739 worktree
740 .as_local_mut()
741 .unwrap()
742 .save_buffer_as(buffer.clone(), path, cx)
743 })
744 .await?;
745 this.update(&mut cx, |this, cx| {
746 this.assign_language_to_buffer(&buffer, Some(&worktree), cx);
747 });
748 Ok(())
749 })
750 }
751
752 #[cfg(any(test, feature = "test-support"))]
753 pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &AppContext) -> bool {
754 let path = path.into();
755 if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
756 self.open_buffers.iter().any(|(_, buffer)| {
757 if let Some(buffer) = buffer.upgrade(cx) {
758 if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
759 if file.worktree == worktree && file.path() == &path.path {
760 return true;
761 }
762 }
763 }
764 false
765 })
766 } else {
767 false
768 }
769 }
770
771 fn get_open_buffer(
772 &mut self,
773 path: &ProjectPath,
774 cx: &mut ModelContext<Self>,
775 ) -> Option<ModelHandle<Buffer>> {
776 let mut result = None;
777 let worktree = self.worktree_for_id(path.worktree_id, cx)?;
778 self.open_buffers.retain(|_, buffer| {
779 if let Some(buffer) = buffer.upgrade(cx) {
780 if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
781 if file.worktree == worktree && file.path() == &path.path {
782 result = Some(buffer);
783 }
784 }
785 true
786 } else {
787 false
788 }
789 });
790 result
791 }
792
793 fn register_buffer(
794 &mut self,
795 buffer: &ModelHandle<Buffer>,
796 worktree: Option<&ModelHandle<Worktree>>,
797 cx: &mut ModelContext<Self>,
798 ) -> Result<()> {
799 match self.open_buffers.insert(
800 buffer.read(cx).remote_id(),
801 OpenBuffer::Loaded(buffer.downgrade()),
802 ) {
803 None => {}
804 Some(OpenBuffer::Loading(operations)) => {
805 buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?
806 }
807 Some(OpenBuffer::Loaded(_)) => Err(anyhow!("registered the same buffer twice"))?,
808 }
809 self.assign_language_to_buffer(&buffer, worktree, cx);
810 Ok(())
811 }
812
813 fn assign_language_to_buffer(
814 &mut self,
815 buffer: &ModelHandle<Buffer>,
816 worktree: Option<&ModelHandle<Worktree>>,
817 cx: &mut ModelContext<Self>,
818 ) -> Option<()> {
819 let (path, full_path) = {
820 let file = buffer.read(cx).file()?;
821 (file.path().clone(), file.full_path(cx))
822 };
823
824 // If the buffer has a language, set it and start/assign the language server
825 if let Some(language) = self.languages.select_language(&full_path) {
826 buffer.update(cx, |buffer, cx| {
827 buffer.set_language(Some(language.clone()), cx);
828 });
829
830 // For local worktrees, start a language server if needed.
831 // Also assign the language server and any previously stored diagnostics to the buffer.
832 if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
833 let worktree_id = local_worktree.id();
834 let worktree_abs_path = local_worktree.abs_path().clone();
835 let buffer = buffer.downgrade();
836 let language_server =
837 self.start_language_server(worktree_id, worktree_abs_path, language, cx);
838
839 cx.spawn_weak(|_, mut cx| async move {
840 if let Some(language_server) = language_server.await {
841 if let Some(buffer) = buffer.upgrade(&cx) {
842 buffer.update(&mut cx, |buffer, cx| {
843 buffer.set_language_server(Some(language_server), cx);
844 });
845 }
846 }
847 })
848 .detach();
849 }
850 }
851
852 if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
853 if let Some(diagnostics) = local_worktree.diagnostics_for_path(&path) {
854 buffer.update(cx, |buffer, cx| {
855 buffer.update_diagnostics(diagnostics, None, cx).log_err();
856 });
857 }
858 }
859
860 None
861 }
862
863 fn start_language_server(
864 &mut self,
865 worktree_id: WorktreeId,
866 worktree_path: Arc<Path>,
867 language: Arc<Language>,
868 cx: &mut ModelContext<Self>,
869 ) -> Shared<Task<Option<Arc<LanguageServer>>>> {
870 enum LspEvent {
871 DiagnosticsStart,
872 DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
873 DiagnosticsFinish,
874 }
875
876 let key = (worktree_id, language.name().to_string());
877 self.started_language_servers
878 .entry(key.clone())
879 .or_insert_with(|| {
880 let language_server = self.languages.start_language_server(
881 &language,
882 worktree_path,
883 self.client.http_client(),
884 cx,
885 );
886 let rpc = self.client.clone();
887 cx.spawn_weak(|this, mut cx| async move {
888 let language_server = language_server?.await.log_err()?;
889 if let Some(this) = this.upgrade(&cx) {
890 this.update(&mut cx, |this, _| {
891 this.language_servers.insert(key, language_server.clone());
892 });
893 }
894
895 let disk_based_sources = language
896 .disk_based_diagnostic_sources()
897 .cloned()
898 .unwrap_or_default();
899 let disk_based_diagnostics_progress_token =
900 language.disk_based_diagnostics_progress_token().cloned();
901 let has_disk_based_diagnostic_progress_token =
902 disk_based_diagnostics_progress_token.is_some();
903 let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
904
905 // Listen for `PublishDiagnostics` notifications.
906 language_server
907 .on_notification::<lsp::notification::PublishDiagnostics, _>({
908 let diagnostics_tx = diagnostics_tx.clone();
909 move |params| {
910 if !has_disk_based_diagnostic_progress_token {
911 block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
912 }
913 block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params)))
914 .ok();
915 if !has_disk_based_diagnostic_progress_token {
916 block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
917 }
918 }
919 })
920 .detach();
921
922 // Listen for `Progress` notifications. Send an event when the language server
923 // transitions between running jobs and not running any jobs.
924 let mut running_jobs_for_this_server: i32 = 0;
925 language_server
926 .on_notification::<lsp::notification::Progress, _>(move |params| {
927 let token = match params.token {
928 lsp::NumberOrString::Number(_) => None,
929 lsp::NumberOrString::String(token) => Some(token),
930 };
931
932 if token == disk_based_diagnostics_progress_token {
933 match params.value {
934 lsp::ProgressParamsValue::WorkDone(progress) => {
935 match progress {
936 lsp::WorkDoneProgress::Begin(_) => {
937 running_jobs_for_this_server += 1;
938 if running_jobs_for_this_server == 1 {
939 block_on(
940 diagnostics_tx
941 .send(LspEvent::DiagnosticsStart),
942 )
943 .ok();
944 }
945 }
946 lsp::WorkDoneProgress::End(_) => {
947 running_jobs_for_this_server -= 1;
948 if running_jobs_for_this_server == 0 {
949 block_on(
950 diagnostics_tx
951 .send(LspEvent::DiagnosticsFinish),
952 )
953 .ok();
954 }
955 }
956 _ => {}
957 }
958 }
959 }
960 }
961 })
962 .detach();
963
964 // Process all the LSP events.
965 cx.spawn(|mut cx| async move {
966 while let Ok(message) = diagnostics_rx.recv().await {
967 let this = this.upgrade(&cx)?;
968 match message {
969 LspEvent::DiagnosticsStart => {
970 this.update(&mut cx, |this, cx| {
971 this.disk_based_diagnostics_started(cx);
972 if let Some(project_id) = this.remote_id() {
973 rpc.send(proto::DiskBasedDiagnosticsUpdating {
974 project_id,
975 })
976 .log_err();
977 }
978 });
979 }
980 LspEvent::DiagnosticsUpdate(mut params) => {
981 language.process_diagnostics(&mut params);
982 this.update(&mut cx, |this, cx| {
983 this.update_diagnostics(params, &disk_based_sources, cx)
984 .log_err();
985 });
986 }
987 LspEvent::DiagnosticsFinish => {
988 this.update(&mut cx, |this, cx| {
989 this.disk_based_diagnostics_finished(cx);
990 if let Some(project_id) = this.remote_id() {
991 rpc.send(proto::DiskBasedDiagnosticsUpdated {
992 project_id,
993 })
994 .log_err();
995 }
996 });
997 }
998 }
999 }
1000 Some(())
1001 })
1002 .detach();
1003
1004 Some(language_server)
1005 })
1006 .shared()
1007 })
1008 .clone()
1009 }
1010
1011 pub fn update_diagnostics(
1012 &mut self,
1013 params: lsp::PublishDiagnosticsParams,
1014 disk_based_sources: &HashSet<String>,
1015 cx: &mut ModelContext<Self>,
1016 ) -> Result<()> {
1017 let abs_path = params
1018 .uri
1019 .to_file_path()
1020 .map_err(|_| anyhow!("URI is not a file"))?;
1021 let mut next_group_id = 0;
1022 let mut diagnostics = Vec::default();
1023 let mut primary_diagnostic_group_ids = HashMap::default();
1024 let mut sources_by_group_id = HashMap::default();
1025 let mut supporting_diagnostic_severities = HashMap::default();
1026 for diagnostic in ¶ms.diagnostics {
1027 let source = diagnostic.source.as_ref();
1028 let code = diagnostic.code.as_ref().map(|code| match code {
1029 lsp::NumberOrString::Number(code) => code.to_string(),
1030 lsp::NumberOrString::String(code) => code.clone(),
1031 });
1032 let range = range_from_lsp(diagnostic.range);
1033 let is_supporting = diagnostic
1034 .related_information
1035 .as_ref()
1036 .map_or(false, |infos| {
1037 infos.iter().any(|info| {
1038 primary_diagnostic_group_ids.contains_key(&(
1039 source,
1040 code.clone(),
1041 range_from_lsp(info.location.range),
1042 ))
1043 })
1044 });
1045
1046 if is_supporting {
1047 if let Some(severity) = diagnostic.severity {
1048 supporting_diagnostic_severities
1049 .insert((source, code.clone(), range), severity);
1050 }
1051 } else {
1052 let group_id = post_inc(&mut next_group_id);
1053 let is_disk_based =
1054 source.map_or(false, |source| disk_based_sources.contains(source));
1055
1056 sources_by_group_id.insert(group_id, source);
1057 primary_diagnostic_group_ids
1058 .insert((source, code.clone(), range.clone()), group_id);
1059
1060 diagnostics.push(DiagnosticEntry {
1061 range,
1062 diagnostic: Diagnostic {
1063 code: code.clone(),
1064 severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
1065 message: diagnostic.message.clone(),
1066 group_id,
1067 is_primary: true,
1068 is_valid: true,
1069 is_disk_based,
1070 },
1071 });
1072 if let Some(infos) = &diagnostic.related_information {
1073 for info in infos {
1074 if info.location.uri == params.uri && !info.message.is_empty() {
1075 let range = range_from_lsp(info.location.range);
1076 diagnostics.push(DiagnosticEntry {
1077 range,
1078 diagnostic: Diagnostic {
1079 code: code.clone(),
1080 severity: DiagnosticSeverity::INFORMATION,
1081 message: info.message.clone(),
1082 group_id,
1083 is_primary: false,
1084 is_valid: true,
1085 is_disk_based,
1086 },
1087 });
1088 }
1089 }
1090 }
1091 }
1092 }
1093
1094 for entry in &mut diagnostics {
1095 let diagnostic = &mut entry.diagnostic;
1096 if !diagnostic.is_primary {
1097 let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap();
1098 if let Some(&severity) = supporting_diagnostic_severities.get(&(
1099 source,
1100 diagnostic.code.clone(),
1101 entry.range.clone(),
1102 )) {
1103 diagnostic.severity = severity;
1104 }
1105 }
1106 }
1107
1108 self.update_diagnostic_entries(abs_path, params.version, diagnostics, cx)?;
1109 Ok(())
1110 }
1111
1112 pub fn update_diagnostic_entries(
1113 &mut self,
1114 abs_path: PathBuf,
1115 version: Option<i32>,
1116 diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
1117 cx: &mut ModelContext<Project>,
1118 ) -> Result<(), anyhow::Error> {
1119 let (worktree, relative_path) = self
1120 .find_local_worktree(&abs_path, cx)
1121 .ok_or_else(|| anyhow!("no worktree found for diagnostics"))?;
1122 let project_path = ProjectPath {
1123 worktree_id: worktree.read(cx).id(),
1124 path: relative_path.into(),
1125 };
1126
1127 for buffer in self.open_buffers.values() {
1128 if let Some(buffer) = buffer.upgrade(cx) {
1129 if buffer
1130 .read(cx)
1131 .file()
1132 .map_or(false, |file| *file.path() == project_path.path)
1133 {
1134 buffer.update(cx, |buffer, cx| {
1135 buffer.update_diagnostics(diagnostics.clone(), version, cx)
1136 })?;
1137 break;
1138 }
1139 }
1140 }
1141 worktree.update(cx, |worktree, cx| {
1142 worktree
1143 .as_local_mut()
1144 .ok_or_else(|| anyhow!("not a local worktree"))?
1145 .update_diagnostics(project_path.path.clone(), diagnostics, cx)
1146 })?;
1147 cx.emit(Event::DiagnosticsUpdated(project_path));
1148 Ok(())
1149 }
1150
1151 pub fn format(
1152 &self,
1153 buffers: HashSet<ModelHandle<Buffer>>,
1154 push_to_history: bool,
1155 cx: &mut ModelContext<Project>,
1156 ) -> Task<Result<ProjectTransaction>> {
1157 let mut local_buffers = Vec::new();
1158 let mut remote_buffers = None;
1159 for buffer_handle in buffers {
1160 let buffer = buffer_handle.read(cx);
1161 let worktree;
1162 if let Some(file) = File::from_dyn(buffer.file()) {
1163 worktree = file.worktree.clone();
1164 if let Some(buffer_abs_path) = file.as_local().map(|f| f.abs_path(cx)) {
1165 let lang_server;
1166 if let Some(lang) = buffer.language() {
1167 if let Some(server) = self
1168 .language_servers
1169 .get(&(worktree.read(cx).id(), lang.name().to_string()))
1170 {
1171 lang_server = server.clone();
1172 } else {
1173 return Task::ready(Ok(Default::default()));
1174 };
1175 } else {
1176 return Task::ready(Ok(Default::default()));
1177 }
1178
1179 local_buffers.push((buffer_handle, buffer_abs_path, lang_server));
1180 } else {
1181 remote_buffers.get_or_insert(Vec::new()).push(buffer_handle);
1182 }
1183 } else {
1184 return Task::ready(Ok(Default::default()));
1185 }
1186 }
1187
1188 let remote_buffers = self.remote_id().zip(remote_buffers);
1189 let client = self.client.clone();
1190
1191 cx.spawn(|this, mut cx| async move {
1192 let mut project_transaction = ProjectTransaction::default();
1193
1194 if let Some((project_id, remote_buffers)) = remote_buffers {
1195 let response = client
1196 .request(proto::FormatBuffers {
1197 project_id,
1198 buffer_ids: remote_buffers
1199 .iter()
1200 .map(|buffer| buffer.read_with(&cx, |buffer, _| buffer.remote_id()))
1201 .collect(),
1202 })
1203 .await?
1204 .transaction
1205 .ok_or_else(|| anyhow!("missing transaction"))?;
1206 project_transaction = this
1207 .update(&mut cx, |this, cx| {
1208 this.deserialize_project_transaction(response, push_to_history, cx)
1209 })
1210 .await?;
1211 }
1212
1213 for (buffer, buffer_abs_path, lang_server) in local_buffers {
1214 let lsp_edits = lang_server
1215 .request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
1216 text_document: lsp::TextDocumentIdentifier::new(
1217 lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
1218 ),
1219 options: Default::default(),
1220 work_done_progress_params: Default::default(),
1221 })
1222 .await?;
1223
1224 if let Some(lsp_edits) = lsp_edits {
1225 let edits = buffer
1226 .update(&mut cx, |buffer, cx| {
1227 buffer.edits_from_lsp(lsp_edits, None, cx)
1228 })
1229 .await?;
1230 buffer.update(&mut cx, |buffer, cx| {
1231 buffer.finalize_last_transaction();
1232 buffer.start_transaction();
1233 for (range, text) in edits {
1234 buffer.edit([range], text, cx);
1235 }
1236 if buffer.end_transaction(cx).is_some() {
1237 let transaction = buffer.finalize_last_transaction().unwrap().clone();
1238 if !push_to_history {
1239 buffer.forget_transaction(transaction.id);
1240 }
1241 project_transaction.0.insert(cx.handle(), transaction);
1242 }
1243 });
1244 }
1245 }
1246
1247 Ok(project_transaction)
1248 })
1249 }
1250
1251 pub fn definition<T: ToPointUtf16>(
1252 &self,
1253 buffer: &ModelHandle<Buffer>,
1254 position: T,
1255 cx: &mut ModelContext<Self>,
1256 ) -> Task<Result<Vec<Definition>>> {
1257 let position = position.to_point_utf16(buffer.read(cx));
1258 self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
1259 }
1260
1261 pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
1262 if self.is_local() {
1263 let mut language_servers = HashMap::default();
1264 for ((worktree_id, language_name), language_server) in self.language_servers.iter() {
1265 if let Some((worktree, language)) = self
1266 .worktree_for_id(*worktree_id, cx)
1267 .and_then(|worktree| worktree.read(cx).as_local())
1268 .zip(self.languages.get_language(language_name))
1269 {
1270 language_servers
1271 .entry(Arc::as_ptr(language_server))
1272 .or_insert((
1273 language_server.clone(),
1274 *worktree_id,
1275 worktree.abs_path().clone(),
1276 language.clone(),
1277 ));
1278 }
1279 }
1280
1281 let mut requests = Vec::new();
1282 for (language_server, _, _, _) in language_servers.values() {
1283 requests.push(language_server.request::<lsp::request::WorkspaceSymbol>(
1284 lsp::WorkspaceSymbolParams {
1285 query: query.to_string(),
1286 ..Default::default()
1287 },
1288 ));
1289 }
1290
1291 cx.spawn_weak(|this, cx| async move {
1292 let responses = futures::future::try_join_all(requests).await?;
1293
1294 let mut symbols = Vec::new();
1295 if let Some(this) = this.upgrade(&cx) {
1296 this.read_with(&cx, |this, cx| {
1297 for ((_, source_worktree_id, worktree_abs_path, language), lsp_symbols) in
1298 language_servers.into_values().zip(responses)
1299 {
1300 symbols.extend(lsp_symbols.into_iter().flatten().filter_map(
1301 |lsp_symbol| {
1302 let abs_path = lsp_symbol.location.uri.to_file_path().ok()?;
1303 let mut worktree_id = source_worktree_id;
1304 let path;
1305 if let Some((worktree, rel_path)) =
1306 this.find_local_worktree(&abs_path, cx)
1307 {
1308 worktree_id = worktree.read(cx).id();
1309 path = rel_path;
1310 } else {
1311 path = relativize_path(&worktree_abs_path, &abs_path);
1312 }
1313
1314 let label = language
1315 .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind)
1316 .unwrap_or_else(|| {
1317 CodeLabel::plain(lsp_symbol.name.clone(), None)
1318 });
1319 let signature = this.symbol_signature(worktree_id, &path);
1320
1321 Some(Symbol {
1322 source_worktree_id,
1323 worktree_id,
1324 language_name: language.name().to_string(),
1325 name: lsp_symbol.name,
1326 kind: lsp_symbol.kind,
1327 label,
1328 path,
1329 range: range_from_lsp(lsp_symbol.location.range),
1330 signature,
1331 })
1332 },
1333 ));
1334 }
1335 })
1336 }
1337
1338 Ok(symbols)
1339 })
1340 } else if let Some(project_id) = self.remote_id() {
1341 let request = self.client.request(proto::GetProjectSymbols {
1342 project_id,
1343 query: query.to_string(),
1344 });
1345 cx.spawn_weak(|this, cx| async move {
1346 let response = request.await?;
1347 let mut symbols = Vec::new();
1348 if let Some(this) = this.upgrade(&cx) {
1349 this.read_with(&cx, |this, _| {
1350 symbols.extend(
1351 response
1352 .symbols
1353 .into_iter()
1354 .filter_map(|symbol| this.deserialize_symbol(symbol).log_err()),
1355 );
1356 })
1357 }
1358 Ok(symbols)
1359 })
1360 } else {
1361 Task::ready(Ok(Default::default()))
1362 }
1363 }
1364
1365 pub fn open_buffer_for_symbol(
1366 &mut self,
1367 symbol: &Symbol,
1368 cx: &mut ModelContext<Self>,
1369 ) -> Task<Result<ModelHandle<Buffer>>> {
1370 if self.is_local() {
1371 let language_server = if let Some(server) = self
1372 .language_servers
1373 .get(&(symbol.source_worktree_id, symbol.language_name.clone()))
1374 {
1375 server.clone()
1376 } else {
1377 return Task::ready(Err(anyhow!(
1378 "language server for worktree and language not found"
1379 )));
1380 };
1381
1382 let worktree_abs_path = if let Some(worktree_abs_path) = self
1383 .worktree_for_id(symbol.worktree_id, cx)
1384 .and_then(|worktree| worktree.read(cx).as_local())
1385 .map(|local_worktree| local_worktree.abs_path())
1386 {
1387 worktree_abs_path
1388 } else {
1389 return Task::ready(Err(anyhow!("worktree not found for symbol")));
1390 };
1391 let symbol_abs_path = worktree_abs_path.join(&symbol.path);
1392 let symbol_uri = if let Ok(uri) = lsp::Url::from_file_path(symbol_abs_path) {
1393 uri
1394 } else {
1395 return Task::ready(Err(anyhow!("invalid symbol path")));
1396 };
1397
1398 self.open_local_buffer_via_lsp(
1399 symbol_uri,
1400 symbol.language_name.clone(),
1401 language_server,
1402 cx,
1403 )
1404 } else if let Some(project_id) = self.remote_id() {
1405 let request = self.client.request(proto::OpenBufferForSymbol {
1406 project_id,
1407 symbol: Some(serialize_symbol(symbol)),
1408 });
1409 cx.spawn(|this, mut cx| async move {
1410 let response = request.await?;
1411 let buffer = response.buffer.ok_or_else(|| anyhow!("invalid buffer"))?;
1412 this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
1413 .await
1414 })
1415 } else {
1416 Task::ready(Err(anyhow!("project does not have a remote id")))
1417 }
1418 }
1419
1420 pub fn completions<T: ToPointUtf16>(
1421 &self,
1422 source_buffer_handle: &ModelHandle<Buffer>,
1423 position: T,
1424 cx: &mut ModelContext<Self>,
1425 ) -> Task<Result<Vec<Completion>>> {
1426 let source_buffer_handle = source_buffer_handle.clone();
1427 let source_buffer = source_buffer_handle.read(cx);
1428 let buffer_id = source_buffer.remote_id();
1429 let language = source_buffer.language().cloned();
1430 let worktree;
1431 let buffer_abs_path;
1432 if let Some(file) = File::from_dyn(source_buffer.file()) {
1433 worktree = file.worktree.clone();
1434 buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
1435 } else {
1436 return Task::ready(Ok(Default::default()));
1437 };
1438
1439 let position = position.to_point_utf16(source_buffer);
1440 let anchor = source_buffer.anchor_after(position);
1441
1442 if worktree.read(cx).as_local().is_some() {
1443 let buffer_abs_path = buffer_abs_path.unwrap();
1444 let lang_server = if let Some(server) = source_buffer.language_server().cloned() {
1445 server
1446 } else {
1447 return Task::ready(Ok(Default::default()));
1448 };
1449
1450 cx.spawn(|_, cx| async move {
1451 let completions = lang_server
1452 .request::<lsp::request::Completion>(lsp::CompletionParams {
1453 text_document_position: lsp::TextDocumentPositionParams::new(
1454 lsp::TextDocumentIdentifier::new(
1455 lsp::Url::from_file_path(buffer_abs_path).unwrap(),
1456 ),
1457 position.to_lsp_position(),
1458 ),
1459 context: Default::default(),
1460 work_done_progress_params: Default::default(),
1461 partial_result_params: Default::default(),
1462 })
1463 .await
1464 .context("lsp completion request failed")?;
1465
1466 let completions = if let Some(completions) = completions {
1467 match completions {
1468 lsp::CompletionResponse::Array(completions) => completions,
1469 lsp::CompletionResponse::List(list) => list.items,
1470 }
1471 } else {
1472 Default::default()
1473 };
1474
1475 source_buffer_handle.read_with(&cx, |this, _| {
1476 Ok(completions
1477 .into_iter()
1478 .filter_map(|lsp_completion| {
1479 let (old_range, new_text) = match lsp_completion.text_edit.as_ref()? {
1480 lsp::CompletionTextEdit::Edit(edit) => {
1481 (range_from_lsp(edit.range), edit.new_text.clone())
1482 }
1483 lsp::CompletionTextEdit::InsertAndReplace(_) => {
1484 log::info!("unsupported insert/replace completion");
1485 return None;
1486 }
1487 };
1488
1489 let clipped_start = this.clip_point_utf16(old_range.start, Bias::Left);
1490 let clipped_end = this.clip_point_utf16(old_range.end, Bias::Left);
1491 if clipped_start == old_range.start && clipped_end == old_range.end {
1492 Some(Completion {
1493 old_range: this.anchor_before(old_range.start)
1494 ..this.anchor_after(old_range.end),
1495 new_text,
1496 label: language
1497 .as_ref()
1498 .and_then(|l| l.label_for_completion(&lsp_completion))
1499 .unwrap_or_else(|| {
1500 CodeLabel::plain(
1501 lsp_completion.label.clone(),
1502 lsp_completion.filter_text.as_deref(),
1503 )
1504 }),
1505 lsp_completion,
1506 })
1507 } else {
1508 None
1509 }
1510 })
1511 .collect())
1512 })
1513 })
1514 } else if let Some(project_id) = self.remote_id() {
1515 let rpc = self.client.clone();
1516 let message = proto::GetCompletions {
1517 project_id,
1518 buffer_id,
1519 position: Some(language::proto::serialize_anchor(&anchor)),
1520 version: (&source_buffer.version()).into(),
1521 };
1522 cx.spawn_weak(|_, mut cx| async move {
1523 let response = rpc.request(message).await?;
1524
1525 source_buffer_handle
1526 .update(&mut cx, |buffer, _| {
1527 buffer.wait_for_version(response.version.into())
1528 })
1529 .await;
1530
1531 response
1532 .completions
1533 .into_iter()
1534 .map(|completion| {
1535 language::proto::deserialize_completion(completion, language.as_ref())
1536 })
1537 .collect()
1538 })
1539 } else {
1540 Task::ready(Ok(Default::default()))
1541 }
1542 }
1543
1544 pub fn apply_additional_edits_for_completion(
1545 &self,
1546 buffer_handle: ModelHandle<Buffer>,
1547 completion: Completion,
1548 push_to_history: bool,
1549 cx: &mut ModelContext<Self>,
1550 ) -> Task<Result<Option<Transaction>>> {
1551 let buffer = buffer_handle.read(cx);
1552 let buffer_id = buffer.remote_id();
1553
1554 if self.is_local() {
1555 let lang_server = if let Some(language_server) = buffer.language_server() {
1556 language_server.clone()
1557 } else {
1558 return Task::ready(Err(anyhow!("buffer does not have a language server")));
1559 };
1560
1561 cx.spawn(|_, mut cx| async move {
1562 let resolved_completion = lang_server
1563 .request::<lsp::request::ResolveCompletionItem>(completion.lsp_completion)
1564 .await?;
1565 if let Some(edits) = resolved_completion.additional_text_edits {
1566 let edits = buffer_handle
1567 .update(&mut cx, |buffer, cx| buffer.edits_from_lsp(edits, None, cx))
1568 .await?;
1569 buffer_handle.update(&mut cx, |buffer, cx| {
1570 buffer.finalize_last_transaction();
1571 buffer.start_transaction();
1572 for (range, text) in edits {
1573 buffer.edit([range], text, cx);
1574 }
1575 let transaction = if buffer.end_transaction(cx).is_some() {
1576 let transaction = buffer.finalize_last_transaction().unwrap().clone();
1577 if !push_to_history {
1578 buffer.forget_transaction(transaction.id);
1579 }
1580 Some(transaction)
1581 } else {
1582 None
1583 };
1584 Ok(transaction)
1585 })
1586 } else {
1587 Ok(None)
1588 }
1589 })
1590 } else if let Some(project_id) = self.remote_id() {
1591 let client = self.client.clone();
1592 cx.spawn(|_, mut cx| async move {
1593 let response = client
1594 .request(proto::ApplyCompletionAdditionalEdits {
1595 project_id,
1596 buffer_id,
1597 completion: Some(language::proto::serialize_completion(&completion)),
1598 })
1599 .await?;
1600
1601 if let Some(transaction) = response.transaction {
1602 let transaction = language::proto::deserialize_transaction(transaction)?;
1603 buffer_handle
1604 .update(&mut cx, |buffer, _| {
1605 buffer.wait_for_edits(transaction.edit_ids.iter().copied())
1606 })
1607 .await;
1608 if push_to_history {
1609 buffer_handle.update(&mut cx, |buffer, _| {
1610 buffer.push_transaction(transaction.clone(), Instant::now());
1611 });
1612 }
1613 Ok(Some(transaction))
1614 } else {
1615 Ok(None)
1616 }
1617 })
1618 } else {
1619 Task::ready(Err(anyhow!("project does not have a remote id")))
1620 }
1621 }
1622
1623 pub fn code_actions<T: ToOffset>(
1624 &self,
1625 buffer_handle: &ModelHandle<Buffer>,
1626 range: Range<T>,
1627 cx: &mut ModelContext<Self>,
1628 ) -> Task<Result<Vec<CodeAction>>> {
1629 let buffer_handle = buffer_handle.clone();
1630 let buffer = buffer_handle.read(cx);
1631 let buffer_id = buffer.remote_id();
1632 let worktree;
1633 let buffer_abs_path;
1634 if let Some(file) = File::from_dyn(buffer.file()) {
1635 worktree = file.worktree.clone();
1636 buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
1637 } else {
1638 return Task::ready(Ok(Default::default()));
1639 };
1640 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
1641
1642 if worktree.read(cx).as_local().is_some() {
1643 let buffer_abs_path = buffer_abs_path.unwrap();
1644 let lang_name;
1645 let lang_server;
1646 if let Some(lang) = buffer.language() {
1647 lang_name = lang.name().to_string();
1648 if let Some(server) = self
1649 .language_servers
1650 .get(&(worktree.read(cx).id(), lang_name.clone()))
1651 {
1652 lang_server = server.clone();
1653 } else {
1654 return Task::ready(Ok(Default::default()));
1655 };
1656 } else {
1657 return Task::ready(Ok(Default::default()));
1658 }
1659
1660 let lsp_range = lsp::Range::new(
1661 range.start.to_point_utf16(buffer).to_lsp_position(),
1662 range.end.to_point_utf16(buffer).to_lsp_position(),
1663 );
1664 cx.foreground().spawn(async move {
1665 Ok(lang_server
1666 .request::<lsp::request::CodeActionRequest>(lsp::CodeActionParams {
1667 text_document: lsp::TextDocumentIdentifier::new(
1668 lsp::Url::from_file_path(buffer_abs_path).unwrap(),
1669 ),
1670 range: lsp_range,
1671 work_done_progress_params: Default::default(),
1672 partial_result_params: Default::default(),
1673 context: lsp::CodeActionContext {
1674 diagnostics: Default::default(),
1675 only: Some(vec![
1676 lsp::CodeActionKind::QUICKFIX,
1677 lsp::CodeActionKind::REFACTOR,
1678 lsp::CodeActionKind::REFACTOR_EXTRACT,
1679 ]),
1680 },
1681 })
1682 .await?
1683 .unwrap_or_default()
1684 .into_iter()
1685 .filter_map(|entry| {
1686 if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
1687 Some(CodeAction {
1688 range: range.clone(),
1689 lsp_action,
1690 })
1691 } else {
1692 None
1693 }
1694 })
1695 .collect())
1696 })
1697 } else if let Some(project_id) = self.remote_id() {
1698 let rpc = self.client.clone();
1699 cx.spawn_weak(|_, mut cx| async move {
1700 let response = rpc
1701 .request(proto::GetCodeActions {
1702 project_id,
1703 buffer_id,
1704 start: Some(language::proto::serialize_anchor(&range.start)),
1705 end: Some(language::proto::serialize_anchor(&range.end)),
1706 })
1707 .await?;
1708
1709 buffer_handle
1710 .update(&mut cx, |buffer, _| {
1711 buffer.wait_for_version(response.version.into())
1712 })
1713 .await;
1714
1715 response
1716 .actions
1717 .into_iter()
1718 .map(language::proto::deserialize_code_action)
1719 .collect()
1720 })
1721 } else {
1722 Task::ready(Ok(Default::default()))
1723 }
1724 }
1725
1726 pub fn apply_code_action(
1727 &self,
1728 buffer_handle: ModelHandle<Buffer>,
1729 mut action: CodeAction,
1730 push_to_history: bool,
1731 cx: &mut ModelContext<Self>,
1732 ) -> Task<Result<ProjectTransaction>> {
1733 if self.is_local() {
1734 let buffer = buffer_handle.read(cx);
1735 let lang_name = if let Some(lang) = buffer.language() {
1736 lang.name().to_string()
1737 } else {
1738 return Task::ready(Ok(Default::default()));
1739 };
1740 let lang_server = if let Some(language_server) = buffer.language_server() {
1741 language_server.clone()
1742 } else {
1743 return Task::ready(Err(anyhow!("buffer does not have a language server")));
1744 };
1745 let range = action.range.to_point_utf16(buffer);
1746
1747 cx.spawn(|this, mut cx| async move {
1748 if let Some(lsp_range) = action
1749 .lsp_action
1750 .data
1751 .as_mut()
1752 .and_then(|d| d.get_mut("codeActionParams"))
1753 .and_then(|d| d.get_mut("range"))
1754 {
1755 *lsp_range = serde_json::to_value(&lsp::Range::new(
1756 range.start.to_lsp_position(),
1757 range.end.to_lsp_position(),
1758 ))
1759 .unwrap();
1760 action.lsp_action = lang_server
1761 .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
1762 .await?;
1763 } else {
1764 let actions = this
1765 .update(&mut cx, |this, cx| {
1766 this.code_actions(&buffer_handle, action.range, cx)
1767 })
1768 .await?;
1769 action.lsp_action = actions
1770 .into_iter()
1771 .find(|a| a.lsp_action.title == action.lsp_action.title)
1772 .ok_or_else(|| anyhow!("code action is outdated"))?
1773 .lsp_action;
1774 }
1775
1776 if let Some(edit) = action.lsp_action.edit {
1777 Self::deserialize_workspace_edit(
1778 this,
1779 edit,
1780 push_to_history,
1781 lang_name,
1782 lang_server,
1783 &mut cx,
1784 )
1785 .await
1786 } else {
1787 Ok(ProjectTransaction::default())
1788 }
1789 })
1790 } else if let Some(project_id) = self.remote_id() {
1791 let client = self.client.clone();
1792 let request = proto::ApplyCodeAction {
1793 project_id,
1794 buffer_id: buffer_handle.read(cx).remote_id(),
1795 action: Some(language::proto::serialize_code_action(&action)),
1796 };
1797 cx.spawn(|this, mut cx| async move {
1798 let response = client
1799 .request(request)
1800 .await?
1801 .transaction
1802 .ok_or_else(|| anyhow!("missing transaction"))?;
1803 this.update(&mut cx, |this, cx| {
1804 this.deserialize_project_transaction(response, push_to_history, cx)
1805 })
1806 .await
1807 })
1808 } else {
1809 Task::ready(Err(anyhow!("project does not have a remote id")))
1810 }
1811 }
1812
1813 async fn deserialize_workspace_edit(
1814 this: ModelHandle<Self>,
1815 edit: lsp::WorkspaceEdit,
1816 push_to_history: bool,
1817 language_name: String,
1818 language_server: Arc<LanguageServer>,
1819 cx: &mut AsyncAppContext,
1820 ) -> Result<ProjectTransaction> {
1821 let fs = this.read_with(cx, |this, _| this.fs.clone());
1822 let mut operations = Vec::new();
1823 if let Some(document_changes) = edit.document_changes {
1824 match document_changes {
1825 lsp::DocumentChanges::Edits(edits) => {
1826 operations.extend(edits.into_iter().map(lsp::DocumentChangeOperation::Edit))
1827 }
1828 lsp::DocumentChanges::Operations(ops) => operations = ops,
1829 }
1830 } else if let Some(changes) = edit.changes {
1831 operations.extend(changes.into_iter().map(|(uri, edits)| {
1832 lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
1833 text_document: lsp::OptionalVersionedTextDocumentIdentifier {
1834 uri,
1835 version: None,
1836 },
1837 edits: edits.into_iter().map(lsp::OneOf::Left).collect(),
1838 })
1839 }));
1840 }
1841
1842 let mut project_transaction = ProjectTransaction::default();
1843 for operation in operations {
1844 match operation {
1845 lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Create(op)) => {
1846 let abs_path = op
1847 .uri
1848 .to_file_path()
1849 .map_err(|_| anyhow!("can't convert URI to path"))?;
1850
1851 if let Some(parent_path) = abs_path.parent() {
1852 fs.create_dir(parent_path).await?;
1853 }
1854 if abs_path.ends_with("/") {
1855 fs.create_dir(&abs_path).await?;
1856 } else {
1857 fs.create_file(&abs_path, op.options.map(Into::into).unwrap_or_default())
1858 .await?;
1859 }
1860 }
1861 lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Rename(op)) => {
1862 let source_abs_path = op
1863 .old_uri
1864 .to_file_path()
1865 .map_err(|_| anyhow!("can't convert URI to path"))?;
1866 let target_abs_path = op
1867 .new_uri
1868 .to_file_path()
1869 .map_err(|_| anyhow!("can't convert URI to path"))?;
1870 fs.rename(
1871 &source_abs_path,
1872 &target_abs_path,
1873 op.options.map(Into::into).unwrap_or_default(),
1874 )
1875 .await?;
1876 }
1877 lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Delete(op)) => {
1878 let abs_path = op
1879 .uri
1880 .to_file_path()
1881 .map_err(|_| anyhow!("can't convert URI to path"))?;
1882 let options = op.options.map(Into::into).unwrap_or_default();
1883 if abs_path.ends_with("/") {
1884 fs.remove_dir(&abs_path, options).await?;
1885 } else {
1886 fs.remove_file(&abs_path, options).await?;
1887 }
1888 }
1889 lsp::DocumentChangeOperation::Edit(op) => {
1890 let buffer_to_edit = this
1891 .update(cx, |this, cx| {
1892 this.open_local_buffer_via_lsp(
1893 op.text_document.uri,
1894 language_name.clone(),
1895 language_server.clone(),
1896 cx,
1897 )
1898 })
1899 .await?;
1900
1901 let edits = buffer_to_edit
1902 .update(cx, |buffer, cx| {
1903 let edits = op.edits.into_iter().map(|edit| match edit {
1904 lsp::OneOf::Left(edit) => edit,
1905 lsp::OneOf::Right(edit) => edit.text_edit,
1906 });
1907 buffer.edits_from_lsp(edits, op.text_document.version, cx)
1908 })
1909 .await?;
1910
1911 let transaction = buffer_to_edit.update(cx, |buffer, cx| {
1912 buffer.finalize_last_transaction();
1913 buffer.start_transaction();
1914 for (range, text) in edits {
1915 buffer.edit([range], text, cx);
1916 }
1917 let transaction = if buffer.end_transaction(cx).is_some() {
1918 let transaction = buffer.finalize_last_transaction().unwrap().clone();
1919 if !push_to_history {
1920 buffer.forget_transaction(transaction.id);
1921 }
1922 Some(transaction)
1923 } else {
1924 None
1925 };
1926
1927 transaction
1928 });
1929 if let Some(transaction) = transaction {
1930 project_transaction.0.insert(buffer_to_edit, transaction);
1931 }
1932 }
1933 }
1934 }
1935
1936 Ok(project_transaction)
1937 }
1938
1939 pub fn prepare_rename<T: ToPointUtf16>(
1940 &self,
1941 buffer: ModelHandle<Buffer>,
1942 position: T,
1943 cx: &mut ModelContext<Self>,
1944 ) -> Task<Result<Option<Range<Anchor>>>> {
1945 let position = position.to_point_utf16(buffer.read(cx));
1946 self.request_lsp(buffer, PrepareRename { position }, cx)
1947 }
1948
1949 pub fn perform_rename<T: ToPointUtf16>(
1950 &self,
1951 buffer: ModelHandle<Buffer>,
1952 position: T,
1953 new_name: String,
1954 push_to_history: bool,
1955 cx: &mut ModelContext<Self>,
1956 ) -> Task<Result<ProjectTransaction>> {
1957 let position = position.to_point_utf16(buffer.read(cx));
1958 self.request_lsp(
1959 buffer,
1960 PerformRename {
1961 position,
1962 new_name,
1963 push_to_history,
1964 },
1965 cx,
1966 )
1967 }
1968
1969 fn request_lsp<R: LspCommand>(
1970 &self,
1971 buffer_handle: ModelHandle<Buffer>,
1972 request: R,
1973 cx: &mut ModelContext<Self>,
1974 ) -> Task<Result<R::Response>>
1975 where
1976 <R::LspRequest as lsp::request::Request>::Result: Send,
1977 {
1978 let buffer = buffer_handle.read(cx);
1979 if self.is_local() {
1980 let file = File::from_dyn(buffer.file()).and_then(File::as_local);
1981 if let Some((file, language_server)) = file.zip(buffer.language_server().cloned()) {
1982 let lsp_params = request.to_lsp(&file.abs_path(cx), cx);
1983 return cx.spawn(|this, cx| async move {
1984 let response = language_server
1985 .request::<R::LspRequest>(lsp_params)
1986 .await
1987 .context("lsp request failed")?;
1988 request
1989 .response_from_lsp(response, this, buffer_handle, cx)
1990 .await
1991 });
1992 }
1993 } else if let Some(project_id) = self.remote_id() {
1994 let rpc = self.client.clone();
1995 let message = request.to_proto(project_id, buffer);
1996 return cx.spawn(|this, cx| async move {
1997 let response = rpc.request(message).await?;
1998 request
1999 .response_from_proto(response, this, buffer_handle, cx)
2000 .await
2001 });
2002 }
2003 Task::ready(Ok(Default::default()))
2004 }
2005
2006 pub fn find_or_create_local_worktree(
2007 &self,
2008 abs_path: impl AsRef<Path>,
2009 weak: bool,
2010 cx: &mut ModelContext<Self>,
2011 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
2012 let abs_path = abs_path.as_ref();
2013 if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) {
2014 Task::ready(Ok((tree.clone(), relative_path.into())))
2015 } else {
2016 let worktree = self.create_local_worktree(abs_path, weak, cx);
2017 cx.foreground()
2018 .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
2019 }
2020 }
2021
2022 fn find_local_worktree(
2023 &self,
2024 abs_path: &Path,
2025 cx: &AppContext,
2026 ) -> Option<(ModelHandle<Worktree>, PathBuf)> {
2027 for tree in self.worktrees(cx) {
2028 if let Some(relative_path) = tree
2029 .read(cx)
2030 .as_local()
2031 .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
2032 {
2033 return Some((tree.clone(), relative_path.into()));
2034 }
2035 }
2036 None
2037 }
2038
2039 pub fn is_shared(&self) -> bool {
2040 match &self.client_state {
2041 ProjectClientState::Local { is_shared, .. } => *is_shared,
2042 ProjectClientState::Remote { .. } => false,
2043 }
2044 }
2045
2046 fn create_local_worktree(
2047 &self,
2048 abs_path: impl AsRef<Path>,
2049 weak: bool,
2050 cx: &mut ModelContext<Self>,
2051 ) -> Task<Result<ModelHandle<Worktree>>> {
2052 let fs = self.fs.clone();
2053 let client = self.client.clone();
2054 let path = Arc::from(abs_path.as_ref());
2055 cx.spawn(|project, mut cx| async move {
2056 let worktree = Worktree::local(client.clone(), path, weak, fs, &mut cx).await?;
2057
2058 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
2059 project.add_worktree(&worktree, cx);
2060 (project.remote_id(), project.is_shared())
2061 });
2062
2063 if let Some(project_id) = remote_project_id {
2064 worktree
2065 .update(&mut cx, |worktree, cx| {
2066 worktree.as_local_mut().unwrap().register(project_id, cx)
2067 })
2068 .await?;
2069 if is_shared {
2070 worktree
2071 .update(&mut cx, |worktree, cx| {
2072 worktree.as_local_mut().unwrap().share(project_id, cx)
2073 })
2074 .await?;
2075 }
2076 }
2077
2078 Ok(worktree)
2079 })
2080 }
2081
2082 pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext<Self>) {
2083 self.worktrees.retain(|worktree| {
2084 worktree
2085 .upgrade(cx)
2086 .map_or(false, |w| w.read(cx).id() != id)
2087 });
2088 cx.notify();
2089 }
2090
2091 fn add_worktree(&mut self, worktree: &ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
2092 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
2093 if worktree.read(cx).is_local() {
2094 cx.subscribe(&worktree, |this, worktree, _, cx| {
2095 this.update_local_worktree_buffers(worktree, cx);
2096 })
2097 .detach();
2098 }
2099
2100 let push_weak_handle = {
2101 let worktree = worktree.read(cx);
2102 worktree.is_local() && worktree.is_weak()
2103 };
2104 if push_weak_handle {
2105 cx.observe_release(&worktree, |this, cx| {
2106 this.worktrees
2107 .retain(|worktree| worktree.upgrade(cx).is_some());
2108 cx.notify();
2109 })
2110 .detach();
2111 self.worktrees
2112 .push(WorktreeHandle::Weak(worktree.downgrade()));
2113 } else {
2114 self.worktrees
2115 .push(WorktreeHandle::Strong(worktree.clone()));
2116 }
2117 cx.notify();
2118 }
2119
2120 fn update_local_worktree_buffers(
2121 &mut self,
2122 worktree_handle: ModelHandle<Worktree>,
2123 cx: &mut ModelContext<Self>,
2124 ) {
2125 let snapshot = worktree_handle.read(cx).snapshot();
2126 let mut buffers_to_delete = Vec::new();
2127 for (buffer_id, buffer) in &self.open_buffers {
2128 if let Some(buffer) = buffer.upgrade(cx) {
2129 buffer.update(cx, |buffer, cx| {
2130 if let Some(old_file) = File::from_dyn(buffer.file()) {
2131 if old_file.worktree != worktree_handle {
2132 return;
2133 }
2134
2135 let new_file = if let Some(entry) = old_file
2136 .entry_id
2137 .and_then(|entry_id| snapshot.entry_for_id(entry_id))
2138 {
2139 File {
2140 is_local: true,
2141 entry_id: Some(entry.id),
2142 mtime: entry.mtime,
2143 path: entry.path.clone(),
2144 worktree: worktree_handle.clone(),
2145 }
2146 } else if let Some(entry) =
2147 snapshot.entry_for_path(old_file.path().as_ref())
2148 {
2149 File {
2150 is_local: true,
2151 entry_id: Some(entry.id),
2152 mtime: entry.mtime,
2153 path: entry.path.clone(),
2154 worktree: worktree_handle.clone(),
2155 }
2156 } else {
2157 File {
2158 is_local: true,
2159 entry_id: None,
2160 path: old_file.path().clone(),
2161 mtime: old_file.mtime(),
2162 worktree: worktree_handle.clone(),
2163 }
2164 };
2165
2166 if let Some(project_id) = self.remote_id() {
2167 self.client
2168 .send(proto::UpdateBufferFile {
2169 project_id,
2170 buffer_id: *buffer_id as u64,
2171 file: Some(new_file.to_proto()),
2172 })
2173 .log_err();
2174 }
2175 buffer.file_updated(Box::new(new_file), cx).detach();
2176 }
2177 });
2178 } else {
2179 buffers_to_delete.push(*buffer_id);
2180 }
2181 }
2182
2183 for buffer_id in buffers_to_delete {
2184 self.open_buffers.remove(&buffer_id);
2185 }
2186 }
2187
2188 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
2189 let new_active_entry = entry.and_then(|project_path| {
2190 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
2191 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
2192 Some(ProjectEntry {
2193 worktree_id: project_path.worktree_id,
2194 entry_id: entry.id,
2195 })
2196 });
2197 if new_active_entry != self.active_entry {
2198 self.active_entry = new_active_entry;
2199 cx.emit(Event::ActiveEntryChanged(new_active_entry));
2200 }
2201 }
2202
2203 pub fn is_running_disk_based_diagnostics(&self) -> bool {
2204 self.language_servers_with_diagnostics_running > 0
2205 }
2206
2207 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
2208 let mut summary = DiagnosticSummary::default();
2209 for (_, path_summary) in self.diagnostic_summaries(cx) {
2210 summary.error_count += path_summary.error_count;
2211 summary.warning_count += path_summary.warning_count;
2212 summary.info_count += path_summary.info_count;
2213 summary.hint_count += path_summary.hint_count;
2214 }
2215 summary
2216 }
2217
2218 pub fn diagnostic_summaries<'a>(
2219 &'a self,
2220 cx: &'a AppContext,
2221 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
2222 self.worktrees(cx).flat_map(move |worktree| {
2223 let worktree = worktree.read(cx);
2224 let worktree_id = worktree.id();
2225 worktree
2226 .diagnostic_summaries()
2227 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
2228 })
2229 }
2230
2231 pub fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext<Self>) {
2232 self.language_servers_with_diagnostics_running += 1;
2233 if self.language_servers_with_diagnostics_running == 1 {
2234 cx.emit(Event::DiskBasedDiagnosticsStarted);
2235 }
2236 }
2237
2238 pub fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext<Self>) {
2239 cx.emit(Event::DiskBasedDiagnosticsUpdated);
2240 self.language_servers_with_diagnostics_running -= 1;
2241 if self.language_servers_with_diagnostics_running == 0 {
2242 cx.emit(Event::DiskBasedDiagnosticsFinished);
2243 }
2244 }
2245
2246 pub fn active_entry(&self) -> Option<ProjectEntry> {
2247 self.active_entry
2248 }
2249
2250 // RPC message handlers
2251
2252 async fn handle_unshare_project(
2253 this: ModelHandle<Self>,
2254 _: TypedEnvelope<proto::UnshareProject>,
2255 _: Arc<Client>,
2256 mut cx: AsyncAppContext,
2257 ) -> Result<()> {
2258 this.update(&mut cx, |this, cx| {
2259 if let ProjectClientState::Remote {
2260 sharing_has_stopped,
2261 ..
2262 } = &mut this.client_state
2263 {
2264 *sharing_has_stopped = true;
2265 this.collaborators.clear();
2266 cx.notify();
2267 } else {
2268 unreachable!()
2269 }
2270 });
2271
2272 Ok(())
2273 }
2274
2275 async fn handle_add_collaborator(
2276 this: ModelHandle<Self>,
2277 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
2278 _: Arc<Client>,
2279 mut cx: AsyncAppContext,
2280 ) -> Result<()> {
2281 let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
2282 let collaborator = envelope
2283 .payload
2284 .collaborator
2285 .take()
2286 .ok_or_else(|| anyhow!("empty collaborator"))?;
2287
2288 let collaborator = Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
2289 this.update(&mut cx, |this, cx| {
2290 this.collaborators
2291 .insert(collaborator.peer_id, collaborator);
2292 cx.notify();
2293 });
2294
2295 Ok(())
2296 }
2297
2298 async fn handle_remove_collaborator(
2299 this: ModelHandle<Self>,
2300 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
2301 _: Arc<Client>,
2302 mut cx: AsyncAppContext,
2303 ) -> Result<()> {
2304 this.update(&mut cx, |this, cx| {
2305 let peer_id = PeerId(envelope.payload.peer_id);
2306 let replica_id = this
2307 .collaborators
2308 .remove(&peer_id)
2309 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
2310 .replica_id;
2311 this.shared_buffers.remove(&peer_id);
2312 for (_, buffer) in &this.open_buffers {
2313 if let Some(buffer) = buffer.upgrade(cx) {
2314 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
2315 }
2316 }
2317 cx.notify();
2318 Ok(())
2319 })
2320 }
2321
2322 async fn handle_share_worktree(
2323 this: ModelHandle<Self>,
2324 envelope: TypedEnvelope<proto::ShareWorktree>,
2325 client: Arc<Client>,
2326 mut cx: AsyncAppContext,
2327 ) -> Result<()> {
2328 this.update(&mut cx, |this, cx| {
2329 let remote_id = this.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
2330 let replica_id = this.replica_id();
2331 let worktree = envelope
2332 .payload
2333 .worktree
2334 .ok_or_else(|| anyhow!("invalid worktree"))?;
2335 let (worktree, load_task) =
2336 Worktree::remote(remote_id, replica_id, worktree, client, cx);
2337 this.add_worktree(&worktree, cx);
2338 load_task.detach();
2339 Ok(())
2340 })
2341 }
2342
2343 async fn handle_unregister_worktree(
2344 this: ModelHandle<Self>,
2345 envelope: TypedEnvelope<proto::UnregisterWorktree>,
2346 _: Arc<Client>,
2347 mut cx: AsyncAppContext,
2348 ) -> Result<()> {
2349 this.update(&mut cx, |this, cx| {
2350 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
2351 this.remove_worktree(worktree_id, cx);
2352 Ok(())
2353 })
2354 }
2355
2356 async fn handle_update_worktree(
2357 this: ModelHandle<Self>,
2358 envelope: TypedEnvelope<proto::UpdateWorktree>,
2359 _: Arc<Client>,
2360 mut cx: AsyncAppContext,
2361 ) -> Result<()> {
2362 this.update(&mut cx, |this, cx| {
2363 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
2364 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
2365 worktree.update(cx, |worktree, _| {
2366 let worktree = worktree.as_remote_mut().unwrap();
2367 worktree.update_from_remote(envelope)
2368 })?;
2369 }
2370 Ok(())
2371 })
2372 }
2373
2374 async fn handle_update_diagnostic_summary(
2375 this: ModelHandle<Self>,
2376 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
2377 _: Arc<Client>,
2378 mut cx: AsyncAppContext,
2379 ) -> Result<()> {
2380 this.update(&mut cx, |this, cx| {
2381 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
2382 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
2383 if let Some(summary) = envelope.payload.summary {
2384 let project_path = ProjectPath {
2385 worktree_id,
2386 path: Path::new(&summary.path).into(),
2387 };
2388 worktree.update(cx, |worktree, _| {
2389 worktree
2390 .as_remote_mut()
2391 .unwrap()
2392 .update_diagnostic_summary(project_path.path.clone(), &summary);
2393 });
2394 cx.emit(Event::DiagnosticsUpdated(project_path));
2395 }
2396 }
2397 Ok(())
2398 })
2399 }
2400
2401 async fn handle_disk_based_diagnostics_updating(
2402 this: ModelHandle<Self>,
2403 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
2404 _: Arc<Client>,
2405 mut cx: AsyncAppContext,
2406 ) -> Result<()> {
2407 this.update(&mut cx, |this, cx| this.disk_based_diagnostics_started(cx));
2408 Ok(())
2409 }
2410
2411 async fn handle_disk_based_diagnostics_updated(
2412 this: ModelHandle<Self>,
2413 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
2414 _: Arc<Client>,
2415 mut cx: AsyncAppContext,
2416 ) -> Result<()> {
2417 this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx));
2418 Ok(())
2419 }
2420
2421 async fn handle_update_buffer(
2422 this: ModelHandle<Self>,
2423 envelope: TypedEnvelope<proto::UpdateBuffer>,
2424 _: Arc<Client>,
2425 mut cx: AsyncAppContext,
2426 ) -> Result<()> {
2427 this.update(&mut cx, |this, cx| {
2428 let payload = envelope.payload.clone();
2429 let buffer_id = payload.buffer_id;
2430 let ops = payload
2431 .operations
2432 .into_iter()
2433 .map(|op| language::proto::deserialize_operation(op))
2434 .collect::<Result<Vec<_>, _>>()?;
2435 let is_remote = this.is_remote();
2436 match this.open_buffers.entry(buffer_id) {
2437 hash_map::Entry::Occupied(mut e) => match e.get_mut() {
2438 OpenBuffer::Loaded(buffer) => {
2439 if let Some(buffer) = buffer.upgrade(cx) {
2440 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
2441 }
2442 }
2443 OpenBuffer::Loading(operations) => operations.extend_from_slice(&ops),
2444 },
2445 hash_map::Entry::Vacant(e) => {
2446 if is_remote && this.loading_buffers.len() > 0 {
2447 e.insert(OpenBuffer::Loading(ops));
2448 }
2449 }
2450 }
2451 Ok(())
2452 })
2453 }
2454
2455 async fn handle_update_buffer_file(
2456 this: ModelHandle<Self>,
2457 envelope: TypedEnvelope<proto::UpdateBufferFile>,
2458 _: Arc<Client>,
2459 mut cx: AsyncAppContext,
2460 ) -> Result<()> {
2461 this.update(&mut cx, |this, cx| {
2462 let payload = envelope.payload.clone();
2463 let buffer_id = payload.buffer_id;
2464 let file = payload.file.ok_or_else(|| anyhow!("invalid file"))?;
2465 let worktree = this
2466 .worktree_for_id(WorktreeId::from_proto(file.worktree_id), cx)
2467 .ok_or_else(|| anyhow!("no such worktree"))?;
2468 let file = File::from_proto(file, worktree.clone(), cx)?;
2469 let buffer = this
2470 .open_buffers
2471 .get_mut(&buffer_id)
2472 .and_then(|b| b.upgrade(cx))
2473 .ok_or_else(|| anyhow!("no such buffer"))?;
2474 buffer.update(cx, |buffer, cx| {
2475 buffer.file_updated(Box::new(file), cx).detach();
2476 });
2477 Ok(())
2478 })
2479 }
2480
2481 async fn handle_save_buffer(
2482 this: ModelHandle<Self>,
2483 envelope: TypedEnvelope<proto::SaveBuffer>,
2484 _: Arc<Client>,
2485 mut cx: AsyncAppContext,
2486 ) -> Result<proto::BufferSaved> {
2487 let buffer_id = envelope.payload.buffer_id;
2488 let sender_id = envelope.original_sender_id()?;
2489 let requested_version = envelope.payload.version.try_into()?;
2490
2491 let (project_id, buffer) = this.update(&mut cx, |this, _| {
2492 let project_id = this.remote_id().ok_or_else(|| anyhow!("not connected"))?;
2493 let buffer = this
2494 .shared_buffers
2495 .get(&sender_id)
2496 .and_then(|shared_buffers| shared_buffers.get(&buffer_id).cloned())
2497 .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?;
2498 Ok::<_, anyhow::Error>((project_id, buffer))
2499 })?;
2500
2501 if !buffer
2502 .read_with(&cx, |buffer, _| buffer.version())
2503 .observed_all(&requested_version)
2504 {
2505 Err(anyhow!("save request depends on unreceived edits"))?;
2506 }
2507
2508 let (saved_version, mtime) = buffer.update(&mut cx, |buffer, cx| buffer.save(cx)).await?;
2509 Ok(proto::BufferSaved {
2510 project_id,
2511 buffer_id,
2512 version: (&saved_version).into(),
2513 mtime: Some(mtime.into()),
2514 })
2515 }
2516
2517 async fn handle_format_buffers(
2518 this: ModelHandle<Self>,
2519 envelope: TypedEnvelope<proto::FormatBuffers>,
2520 _: Arc<Client>,
2521 mut cx: AsyncAppContext,
2522 ) -> Result<proto::FormatBuffersResponse> {
2523 let sender_id = envelope.original_sender_id()?;
2524 let format = this.update(&mut cx, |this, cx| {
2525 let shared_buffers = this
2526 .shared_buffers
2527 .get(&sender_id)
2528 .ok_or_else(|| anyhow!("peer has no buffers"))?;
2529 let mut buffers = HashSet::default();
2530 for buffer_id in &envelope.payload.buffer_ids {
2531 buffers.insert(
2532 shared_buffers
2533 .get(buffer_id)
2534 .cloned()
2535 .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
2536 );
2537 }
2538 Ok::<_, anyhow::Error>(this.format(buffers, false, cx))
2539 })?;
2540
2541 let project_transaction = format.await?;
2542 let project_transaction = this.update(&mut cx, |this, cx| {
2543 this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
2544 });
2545 Ok(proto::FormatBuffersResponse {
2546 transaction: Some(project_transaction),
2547 })
2548 }
2549
2550 async fn handle_get_completions(
2551 this: ModelHandle<Self>,
2552 envelope: TypedEnvelope<proto::GetCompletions>,
2553 _: Arc<Client>,
2554 mut cx: AsyncAppContext,
2555 ) -> Result<proto::GetCompletionsResponse> {
2556 let sender_id = envelope.original_sender_id()?;
2557 let position = envelope
2558 .payload
2559 .position
2560 .and_then(language::proto::deserialize_anchor)
2561 .ok_or_else(|| anyhow!("invalid position"))?;
2562 let version = clock::Global::from(envelope.payload.version);
2563 let buffer = this.read_with(&cx, |this, _| {
2564 this.shared_buffers
2565 .get(&sender_id)
2566 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
2567 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
2568 })?;
2569 if !buffer
2570 .read_with(&cx, |buffer, _| buffer.version())
2571 .observed_all(&version)
2572 {
2573 Err(anyhow!("completion request depends on unreceived edits"))?;
2574 }
2575 let version = buffer.read_with(&cx, |buffer, _| buffer.version());
2576 let completions = this
2577 .update(&mut cx, |this, cx| this.completions(&buffer, position, cx))
2578 .await?;
2579
2580 Ok(proto::GetCompletionsResponse {
2581 completions: completions
2582 .iter()
2583 .map(language::proto::serialize_completion)
2584 .collect(),
2585 version: (&version).into(),
2586 })
2587 }
2588
2589 async fn handle_apply_additional_edits_for_completion(
2590 this: ModelHandle<Self>,
2591 envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
2592 _: Arc<Client>,
2593 mut cx: AsyncAppContext,
2594 ) -> Result<proto::ApplyCompletionAdditionalEditsResponse> {
2595 let sender_id = envelope.original_sender_id()?;
2596 let apply_additional_edits = this.update(&mut cx, |this, cx| {
2597 let buffer = this
2598 .shared_buffers
2599 .get(&sender_id)
2600 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
2601 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
2602 let language = buffer.read(cx).language();
2603 let completion = language::proto::deserialize_completion(
2604 envelope
2605 .payload
2606 .completion
2607 .ok_or_else(|| anyhow!("invalid completion"))?,
2608 language,
2609 )?;
2610 Ok::<_, anyhow::Error>(
2611 this.apply_additional_edits_for_completion(buffer, completion, false, cx),
2612 )
2613 })?;
2614
2615 Ok(proto::ApplyCompletionAdditionalEditsResponse {
2616 transaction: apply_additional_edits
2617 .await?
2618 .as_ref()
2619 .map(language::proto::serialize_transaction),
2620 })
2621 }
2622
2623 async fn handle_get_code_actions(
2624 this: ModelHandle<Self>,
2625 envelope: TypedEnvelope<proto::GetCodeActions>,
2626 _: Arc<Client>,
2627 mut cx: AsyncAppContext,
2628 ) -> Result<proto::GetCodeActionsResponse> {
2629 let sender_id = envelope.original_sender_id()?;
2630 let start = envelope
2631 .payload
2632 .start
2633 .and_then(language::proto::deserialize_anchor)
2634 .ok_or_else(|| anyhow!("invalid start"))?;
2635 let end = envelope
2636 .payload
2637 .end
2638 .and_then(language::proto::deserialize_anchor)
2639 .ok_or_else(|| anyhow!("invalid end"))?;
2640 let buffer = this.update(&mut cx, |this, _| {
2641 this.shared_buffers
2642 .get(&sender_id)
2643 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
2644 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
2645 })?;
2646 let version = buffer.read_with(&cx, |buffer, _| buffer.version());
2647 if !version.observed(start.timestamp) || !version.observed(end.timestamp) {
2648 Err(anyhow!("code action request references unreceived edits"))?;
2649 }
2650 let code_actions = this.update(&mut cx, |this, cx| {
2651 Ok::<_, anyhow::Error>(this.code_actions(&buffer, start..end, cx))
2652 })?;
2653
2654 Ok(proto::GetCodeActionsResponse {
2655 actions: code_actions
2656 .await?
2657 .iter()
2658 .map(language::proto::serialize_code_action)
2659 .collect(),
2660 version: (&version).into(),
2661 })
2662 }
2663
2664 async fn handle_apply_code_action(
2665 this: ModelHandle<Self>,
2666 envelope: TypedEnvelope<proto::ApplyCodeAction>,
2667 _: Arc<Client>,
2668 mut cx: AsyncAppContext,
2669 ) -> Result<proto::ApplyCodeActionResponse> {
2670 let sender_id = envelope.original_sender_id()?;
2671 let action = language::proto::deserialize_code_action(
2672 envelope
2673 .payload
2674 .action
2675 .ok_or_else(|| anyhow!("invalid action"))?,
2676 )?;
2677 let apply_code_action = this.update(&mut cx, |this, cx| {
2678 let buffer = this
2679 .shared_buffers
2680 .get(&sender_id)
2681 .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
2682 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
2683 Ok::<_, anyhow::Error>(this.apply_code_action(buffer, action, false, cx))
2684 })?;
2685
2686 let project_transaction = apply_code_action.await?;
2687 let project_transaction = this.update(&mut cx, |this, cx| {
2688 this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
2689 });
2690 Ok(proto::ApplyCodeActionResponse {
2691 transaction: Some(project_transaction),
2692 })
2693 }
2694
2695 async fn handle_lsp_command<T: LspCommand>(
2696 this: ModelHandle<Self>,
2697 envelope: TypedEnvelope<T::ProtoRequest>,
2698 _: Arc<Client>,
2699 mut cx: AsyncAppContext,
2700 ) -> Result<<T::ProtoRequest as proto::RequestMessage>::Response>
2701 where
2702 <T::LspRequest as lsp::request::Request>::Result: Send,
2703 {
2704 let sender_id = envelope.original_sender_id()?;
2705 let (request, buffer_version) = this.update(&mut cx, |this, cx| {
2706 let buffer_id = T::buffer_id_from_proto(&envelope.payload);
2707 let buffer_handle = this
2708 .shared_buffers
2709 .get(&sender_id)
2710 .and_then(|shared_buffers| shared_buffers.get(&buffer_id).cloned())
2711 .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?;
2712 let buffer = buffer_handle.read(cx);
2713 let buffer_version = buffer.version();
2714 let request = T::from_proto(envelope.payload, this, buffer)?;
2715 Ok::<_, anyhow::Error>((this.request_lsp(buffer_handle, request, cx), buffer_version))
2716 })?;
2717 let response = request.await?;
2718 this.update(&mut cx, |this, cx| {
2719 Ok(T::response_to_proto(
2720 response,
2721 this,
2722 sender_id,
2723 &buffer_version,
2724 cx,
2725 ))
2726 })
2727 }
2728
2729 async fn handle_get_project_symbols(
2730 this: ModelHandle<Self>,
2731 envelope: TypedEnvelope<proto::GetProjectSymbols>,
2732 _: Arc<Client>,
2733 mut cx: AsyncAppContext,
2734 ) -> Result<proto::GetProjectSymbolsResponse> {
2735 let symbols = this
2736 .update(&mut cx, |this, cx| {
2737 this.symbols(&envelope.payload.query, cx)
2738 })
2739 .await?;
2740
2741 Ok(proto::GetProjectSymbolsResponse {
2742 symbols: symbols.iter().map(serialize_symbol).collect(),
2743 })
2744 }
2745
2746 async fn handle_open_buffer_for_symbol(
2747 this: ModelHandle<Self>,
2748 envelope: TypedEnvelope<proto::OpenBufferForSymbol>,
2749 _: Arc<Client>,
2750 mut cx: AsyncAppContext,
2751 ) -> Result<proto::OpenBufferForSymbolResponse> {
2752 let peer_id = envelope.original_sender_id()?;
2753 let symbol = envelope
2754 .payload
2755 .symbol
2756 .ok_or_else(|| anyhow!("invalid symbol"))?;
2757 let symbol = this.read_with(&cx, |this, _| {
2758 let symbol = this.deserialize_symbol(symbol)?;
2759 let signature = this.symbol_signature(symbol.worktree_id, &symbol.path);
2760 if signature == symbol.signature {
2761 Ok(symbol)
2762 } else {
2763 Err(anyhow!("invalid symbol signature"))
2764 }
2765 })?;
2766 let buffer = this
2767 .update(&mut cx, |this, cx| this.open_buffer_for_symbol(&symbol, cx))
2768 .await?;
2769
2770 Ok(proto::OpenBufferForSymbolResponse {
2771 buffer: Some(this.update(&mut cx, |this, cx| {
2772 this.serialize_buffer_for_peer(&buffer, peer_id, cx)
2773 })),
2774 })
2775 }
2776
2777 fn symbol_signature(&self, worktree_id: WorktreeId, path: &Path) -> [u8; 32] {
2778 let mut hasher = Sha256::new();
2779 hasher.update(worktree_id.to_proto().to_be_bytes());
2780 hasher.update(path.to_string_lossy().as_bytes());
2781 hasher.update(self.nonce.to_be_bytes());
2782 hasher.finalize().as_slice().try_into().unwrap()
2783 }
2784
2785 async fn handle_open_buffer(
2786 this: ModelHandle<Self>,
2787 envelope: TypedEnvelope<proto::OpenBuffer>,
2788 _: Arc<Client>,
2789 mut cx: AsyncAppContext,
2790 ) -> Result<proto::OpenBufferResponse> {
2791 let peer_id = envelope.original_sender_id()?;
2792 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
2793 let open_buffer = this.update(&mut cx, |this, cx| {
2794 this.open_buffer(
2795 ProjectPath {
2796 worktree_id,
2797 path: PathBuf::from(envelope.payload.path).into(),
2798 },
2799 cx,
2800 )
2801 });
2802
2803 let buffer = open_buffer.await?;
2804 this.update(&mut cx, |this, cx| {
2805 Ok(proto::OpenBufferResponse {
2806 buffer: Some(this.serialize_buffer_for_peer(&buffer, peer_id, cx)),
2807 })
2808 })
2809 }
2810
2811 fn serialize_project_transaction_for_peer(
2812 &mut self,
2813 project_transaction: ProjectTransaction,
2814 peer_id: PeerId,
2815 cx: &AppContext,
2816 ) -> proto::ProjectTransaction {
2817 let mut serialized_transaction = proto::ProjectTransaction {
2818 buffers: Default::default(),
2819 transactions: Default::default(),
2820 };
2821 for (buffer, transaction) in project_transaction.0 {
2822 serialized_transaction
2823 .buffers
2824 .push(self.serialize_buffer_for_peer(&buffer, peer_id, cx));
2825 serialized_transaction
2826 .transactions
2827 .push(language::proto::serialize_transaction(&transaction));
2828 }
2829 serialized_transaction
2830 }
2831
2832 fn deserialize_project_transaction(
2833 &mut self,
2834 message: proto::ProjectTransaction,
2835 push_to_history: bool,
2836 cx: &mut ModelContext<Self>,
2837 ) -> Task<Result<ProjectTransaction>> {
2838 cx.spawn(|this, mut cx| async move {
2839 let mut project_transaction = ProjectTransaction::default();
2840 for (buffer, transaction) in message.buffers.into_iter().zip(message.transactions) {
2841 let buffer = this
2842 .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
2843 .await?;
2844 let transaction = language::proto::deserialize_transaction(transaction)?;
2845 project_transaction.0.insert(buffer, transaction);
2846 }
2847 for (buffer, transaction) in &project_transaction.0 {
2848 buffer
2849 .update(&mut cx, |buffer, _| {
2850 buffer.wait_for_edits(transaction.edit_ids.iter().copied())
2851 })
2852 .await;
2853
2854 if push_to_history {
2855 buffer.update(&mut cx, |buffer, _| {
2856 buffer.push_transaction(transaction.clone(), Instant::now());
2857 });
2858 }
2859 }
2860
2861 Ok(project_transaction)
2862 })
2863 }
2864
2865 fn serialize_buffer_for_peer(
2866 &mut self,
2867 buffer: &ModelHandle<Buffer>,
2868 peer_id: PeerId,
2869 cx: &AppContext,
2870 ) -> proto::Buffer {
2871 let buffer_id = buffer.read(cx).remote_id();
2872 let shared_buffers = self.shared_buffers.entry(peer_id).or_default();
2873 match shared_buffers.entry(buffer_id) {
2874 hash_map::Entry::Occupied(_) => proto::Buffer {
2875 variant: Some(proto::buffer::Variant::Id(buffer_id)),
2876 },
2877 hash_map::Entry::Vacant(entry) => {
2878 entry.insert(buffer.clone());
2879 proto::Buffer {
2880 variant: Some(proto::buffer::Variant::State(buffer.read(cx).to_proto())),
2881 }
2882 }
2883 }
2884 }
2885
2886 fn deserialize_buffer(
2887 &mut self,
2888 buffer: proto::Buffer,
2889 cx: &mut ModelContext<Self>,
2890 ) -> Task<Result<ModelHandle<Buffer>>> {
2891 let replica_id = self.replica_id();
2892
2893 let mut opened_buffer_tx = self.opened_buffer.clone();
2894 let mut opened_buffer_rx = self.opened_buffer.subscribe();
2895 cx.spawn(|this, mut cx| async move {
2896 match buffer.variant.ok_or_else(|| anyhow!("missing buffer"))? {
2897 proto::buffer::Variant::Id(id) => {
2898 let buffer = loop {
2899 let buffer = this.read_with(&cx, |this, cx| {
2900 this.open_buffers
2901 .get(&id)
2902 .and_then(|buffer| buffer.upgrade(cx))
2903 });
2904 if let Some(buffer) = buffer {
2905 break buffer;
2906 }
2907 opened_buffer_rx
2908 .recv()
2909 .await
2910 .ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?;
2911 };
2912 Ok(buffer)
2913 }
2914 proto::buffer::Variant::State(mut buffer) => {
2915 let mut buffer_worktree = None;
2916 let mut buffer_file = None;
2917 if let Some(file) = buffer.file.take() {
2918 this.read_with(&cx, |this, cx| {
2919 let worktree_id = WorktreeId::from_proto(file.worktree_id);
2920 let worktree =
2921 this.worktree_for_id(worktree_id, cx).ok_or_else(|| {
2922 anyhow!("no worktree found for id {}", file.worktree_id)
2923 })?;
2924 buffer_file =
2925 Some(Box::new(File::from_proto(file, worktree.clone(), cx)?)
2926 as Box<dyn language::File>);
2927 buffer_worktree = Some(worktree);
2928 Ok::<_, anyhow::Error>(())
2929 })?;
2930 }
2931
2932 let buffer = cx.add_model(|cx| {
2933 Buffer::from_proto(replica_id, buffer, buffer_file, cx).unwrap()
2934 });
2935 this.update(&mut cx, |this, cx| {
2936 this.register_buffer(&buffer, buffer_worktree.as_ref(), cx)
2937 })?;
2938
2939 let _ = opened_buffer_tx.send(()).await;
2940 Ok(buffer)
2941 }
2942 }
2943 })
2944 }
2945
2946 fn deserialize_symbol(&self, serialized_symbol: proto::Symbol) -> Result<Symbol> {
2947 let language = self
2948 .languages
2949 .get_language(&serialized_symbol.language_name);
2950 let start = serialized_symbol
2951 .start
2952 .ok_or_else(|| anyhow!("invalid start"))?;
2953 let end = serialized_symbol
2954 .end
2955 .ok_or_else(|| anyhow!("invalid end"))?;
2956 let kind = unsafe { mem::transmute(serialized_symbol.kind) };
2957 Ok(Symbol {
2958 source_worktree_id: WorktreeId::from_proto(serialized_symbol.source_worktree_id),
2959 worktree_id: WorktreeId::from_proto(serialized_symbol.worktree_id),
2960 language_name: serialized_symbol.language_name.clone(),
2961 label: language
2962 .and_then(|language| language.label_for_symbol(&serialized_symbol.name, kind))
2963 .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None)),
2964 name: serialized_symbol.name,
2965 path: PathBuf::from(serialized_symbol.path),
2966 range: PointUtf16::new(start.row, start.column)..PointUtf16::new(end.row, end.column),
2967 kind,
2968 signature: serialized_symbol
2969 .signature
2970 .try_into()
2971 .map_err(|_| anyhow!("invalid signature"))?,
2972 })
2973 }
2974
2975 async fn handle_close_buffer(
2976 this: ModelHandle<Self>,
2977 envelope: TypedEnvelope<proto::CloseBuffer>,
2978 _: Arc<Client>,
2979 mut cx: AsyncAppContext,
2980 ) -> Result<()> {
2981 this.update(&mut cx, |this, cx| {
2982 if let Some(shared_buffers) =
2983 this.shared_buffers.get_mut(&envelope.original_sender_id()?)
2984 {
2985 shared_buffers.remove(&envelope.payload.buffer_id);
2986 cx.notify();
2987 }
2988 Ok(())
2989 })
2990 }
2991
2992 async fn handle_buffer_saved(
2993 this: ModelHandle<Self>,
2994 envelope: TypedEnvelope<proto::BufferSaved>,
2995 _: Arc<Client>,
2996 mut cx: AsyncAppContext,
2997 ) -> Result<()> {
2998 let version = envelope.payload.version.try_into()?;
2999 let mtime = envelope
3000 .payload
3001 .mtime
3002 .ok_or_else(|| anyhow!("missing mtime"))?
3003 .into();
3004
3005 this.update(&mut cx, |this, cx| {
3006 let buffer = this
3007 .open_buffers
3008 .get(&envelope.payload.buffer_id)
3009 .and_then(|buffer| buffer.upgrade(cx));
3010 if let Some(buffer) = buffer {
3011 buffer.update(cx, |buffer, cx| {
3012 buffer.did_save(version, mtime, None, cx);
3013 });
3014 }
3015 Ok(())
3016 })
3017 }
3018
3019 async fn handle_buffer_reloaded(
3020 this: ModelHandle<Self>,
3021 envelope: TypedEnvelope<proto::BufferReloaded>,
3022 _: Arc<Client>,
3023 mut cx: AsyncAppContext,
3024 ) -> Result<()> {
3025 let payload = envelope.payload.clone();
3026 let version = payload.version.try_into()?;
3027 let mtime = payload
3028 .mtime
3029 .ok_or_else(|| anyhow!("missing mtime"))?
3030 .into();
3031 this.update(&mut cx, |this, cx| {
3032 let buffer = this
3033 .open_buffers
3034 .get(&payload.buffer_id)
3035 .and_then(|buffer| buffer.upgrade(cx));
3036 if let Some(buffer) = buffer {
3037 buffer.update(cx, |buffer, cx| {
3038 buffer.did_reload(version, mtime, cx);
3039 });
3040 }
3041 Ok(())
3042 })
3043 }
3044
3045 pub fn match_paths<'a>(
3046 &self,
3047 query: &'a str,
3048 include_ignored: bool,
3049 smart_case: bool,
3050 max_results: usize,
3051 cancel_flag: &'a AtomicBool,
3052 cx: &AppContext,
3053 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
3054 let worktrees = self
3055 .worktrees(cx)
3056 .filter(|worktree| !worktree.read(cx).is_weak())
3057 .collect::<Vec<_>>();
3058 let include_root_name = worktrees.len() > 1;
3059 let candidate_sets = worktrees
3060 .into_iter()
3061 .map(|worktree| CandidateSet {
3062 snapshot: worktree.read(cx).snapshot(),
3063 include_ignored,
3064 include_root_name,
3065 })
3066 .collect::<Vec<_>>();
3067
3068 let background = cx.background().clone();
3069 async move {
3070 fuzzy::match_paths(
3071 candidate_sets.as_slice(),
3072 query,
3073 smart_case,
3074 max_results,
3075 cancel_flag,
3076 background,
3077 )
3078 .await
3079 }
3080 }
3081}
3082
3083impl WorktreeHandle {
3084 pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
3085 match self {
3086 WorktreeHandle::Strong(handle) => Some(handle.clone()),
3087 WorktreeHandle::Weak(handle) => handle.upgrade(cx),
3088 }
3089 }
3090}
3091
3092impl OpenBuffer {
3093 pub fn upgrade(&self, cx: &impl UpgradeModelHandle) -> Option<ModelHandle<Buffer>> {
3094 match self {
3095 OpenBuffer::Loaded(handle) => handle.upgrade(cx),
3096 OpenBuffer::Loading(_) => None,
3097 }
3098 }
3099}
3100
3101struct CandidateSet {
3102 snapshot: Snapshot,
3103 include_ignored: bool,
3104 include_root_name: bool,
3105}
3106
3107impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
3108 type Candidates = CandidateSetIter<'a>;
3109
3110 fn id(&self) -> usize {
3111 self.snapshot.id().to_usize()
3112 }
3113
3114 fn len(&self) -> usize {
3115 if self.include_ignored {
3116 self.snapshot.file_count()
3117 } else {
3118 self.snapshot.visible_file_count()
3119 }
3120 }
3121
3122 fn prefix(&self) -> Arc<str> {
3123 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
3124 self.snapshot.root_name().into()
3125 } else if self.include_root_name {
3126 format!("{}/", self.snapshot.root_name()).into()
3127 } else {
3128 "".into()
3129 }
3130 }
3131
3132 fn candidates(&'a self, start: usize) -> Self::Candidates {
3133 CandidateSetIter {
3134 traversal: self.snapshot.files(self.include_ignored, start),
3135 }
3136 }
3137}
3138
3139struct CandidateSetIter<'a> {
3140 traversal: Traversal<'a>,
3141}
3142
3143impl<'a> Iterator for CandidateSetIter<'a> {
3144 type Item = PathMatchCandidate<'a>;
3145
3146 fn next(&mut self) -> Option<Self::Item> {
3147 self.traversal.next().map(|entry| {
3148 if let EntryKind::File(char_bag) = entry.kind {
3149 PathMatchCandidate {
3150 path: &entry.path,
3151 char_bag,
3152 }
3153 } else {
3154 unreachable!()
3155 }
3156 })
3157 }
3158}
3159
3160impl Entity for Project {
3161 type Event = Event;
3162
3163 fn release(&mut self, _: &mut gpui::MutableAppContext) {
3164 match &self.client_state {
3165 ProjectClientState::Local { remote_id_rx, .. } => {
3166 if let Some(project_id) = *remote_id_rx.borrow() {
3167 self.client
3168 .send(proto::UnregisterProject { project_id })
3169 .log_err();
3170 }
3171 }
3172 ProjectClientState::Remote { remote_id, .. } => {
3173 self.client
3174 .send(proto::LeaveProject {
3175 project_id: *remote_id,
3176 })
3177 .log_err();
3178 }
3179 }
3180 }
3181
3182 fn app_will_quit(
3183 &mut self,
3184 _: &mut MutableAppContext,
3185 ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
3186 let shutdown_futures = self
3187 .language_servers
3188 .drain()
3189 .filter_map(|(_, server)| server.shutdown())
3190 .collect::<Vec<_>>();
3191 Some(
3192 async move {
3193 futures::future::join_all(shutdown_futures).await;
3194 }
3195 .boxed(),
3196 )
3197 }
3198}
3199
3200impl Collaborator {
3201 fn from_proto(
3202 message: proto::Collaborator,
3203 user_store: &ModelHandle<UserStore>,
3204 cx: &mut AsyncAppContext,
3205 ) -> impl Future<Output = Result<Self>> {
3206 let user = user_store.update(cx, |user_store, cx| {
3207 user_store.fetch_user(message.user_id, cx)
3208 });
3209
3210 async move {
3211 Ok(Self {
3212 peer_id: PeerId(message.peer_id),
3213 user: user.await?,
3214 replica_id: message.replica_id as ReplicaId,
3215 })
3216 }
3217 }
3218}
3219
3220impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
3221 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
3222 Self {
3223 worktree_id,
3224 path: path.as_ref().into(),
3225 }
3226 }
3227}
3228
3229impl From<lsp::CreateFileOptions> for fs::CreateOptions {
3230 fn from(options: lsp::CreateFileOptions) -> Self {
3231 Self {
3232 overwrite: options.overwrite.unwrap_or(false),
3233 ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
3234 }
3235 }
3236}
3237
3238impl From<lsp::RenameFileOptions> for fs::RenameOptions {
3239 fn from(options: lsp::RenameFileOptions) -> Self {
3240 Self {
3241 overwrite: options.overwrite.unwrap_or(false),
3242 ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
3243 }
3244 }
3245}
3246
3247impl From<lsp::DeleteFileOptions> for fs::RemoveOptions {
3248 fn from(options: lsp::DeleteFileOptions) -> Self {
3249 Self {
3250 recursive: options.recursive.unwrap_or(false),
3251 ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
3252 }
3253 }
3254}
3255
3256fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
3257 proto::Symbol {
3258 source_worktree_id: symbol.source_worktree_id.to_proto(),
3259 worktree_id: symbol.worktree_id.to_proto(),
3260 language_name: symbol.language_name.clone(),
3261 name: symbol.name.clone(),
3262 kind: unsafe { mem::transmute(symbol.kind) },
3263 path: symbol.path.to_string_lossy().to_string(),
3264 start: Some(proto::Point {
3265 row: symbol.range.start.row,
3266 column: symbol.range.start.column,
3267 }),
3268 end: Some(proto::Point {
3269 row: symbol.range.end.row,
3270 column: symbol.range.end.column,
3271 }),
3272 signature: symbol.signature.to_vec(),
3273 }
3274}
3275
3276fn relativize_path(base: &Path, path: &Path) -> PathBuf {
3277 let mut path_components = path.components();
3278 let mut base_components = base.components();
3279 let mut components: Vec<Component> = Vec::new();
3280 loop {
3281 match (path_components.next(), base_components.next()) {
3282 (None, None) => break,
3283 (Some(a), None) => {
3284 components.push(a);
3285 components.extend(path_components.by_ref());
3286 break;
3287 }
3288 (None, _) => components.push(Component::ParentDir),
3289 (Some(a), Some(b)) if components.is_empty() && a == b => (),
3290 (Some(a), Some(b)) if b == Component::CurDir => components.push(a),
3291 (Some(a), Some(_)) => {
3292 components.push(Component::ParentDir);
3293 for _ in base_components {
3294 components.push(Component::ParentDir);
3295 }
3296 components.push(a);
3297 components.extend(path_components.by_ref());
3298 break;
3299 }
3300 }
3301 }
3302 components.iter().map(|c| c.as_os_str()).collect()
3303}
3304
3305#[cfg(test)]
3306mod tests {
3307 use super::{Event, *};
3308 use fs::RealFs;
3309 use futures::StreamExt;
3310 use gpui::test::subscribe;
3311 use language::{
3312 tree_sitter_rust, AnchorRangeExt, Diagnostic, LanguageConfig, LanguageServerConfig, Point,
3313 };
3314 use lsp::Url;
3315 use serde_json::json;
3316 use std::{cell::RefCell, os::unix, path::PathBuf, rc::Rc};
3317 use unindent::Unindent as _;
3318 use util::test::temp_tree;
3319 use worktree::WorktreeHandle as _;
3320
3321 #[gpui::test]
3322 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
3323 let dir = temp_tree(json!({
3324 "root": {
3325 "apple": "",
3326 "banana": {
3327 "carrot": {
3328 "date": "",
3329 "endive": "",
3330 }
3331 },
3332 "fennel": {
3333 "grape": "",
3334 }
3335 }
3336 }));
3337
3338 let root_link_path = dir.path().join("root_link");
3339 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
3340 unix::fs::symlink(
3341 &dir.path().join("root/fennel"),
3342 &dir.path().join("root/finnochio"),
3343 )
3344 .unwrap();
3345
3346 let project = Project::test(Arc::new(RealFs), &mut cx);
3347
3348 let (tree, _) = project
3349 .update(&mut cx, |project, cx| {
3350 project.find_or_create_local_worktree(&root_link_path, false, cx)
3351 })
3352 .await
3353 .unwrap();
3354
3355 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3356 .await;
3357 cx.read(|cx| {
3358 let tree = tree.read(cx);
3359 assert_eq!(tree.file_count(), 5);
3360 assert_eq!(
3361 tree.inode_for_path("fennel/grape"),
3362 tree.inode_for_path("finnochio/grape")
3363 );
3364 });
3365
3366 let cancel_flag = Default::default();
3367 let results = project
3368 .read_with(&cx, |project, cx| {
3369 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
3370 })
3371 .await;
3372 assert_eq!(
3373 results
3374 .into_iter()
3375 .map(|result| result.path)
3376 .collect::<Vec<Arc<Path>>>(),
3377 vec![
3378 PathBuf::from("banana/carrot/date").into(),
3379 PathBuf::from("banana/carrot/endive").into(),
3380 ]
3381 );
3382 }
3383
3384 #[gpui::test]
3385 async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
3386 let (language_server_config, mut fake_servers) = LanguageServerConfig::fake();
3387 let progress_token = language_server_config
3388 .disk_based_diagnostics_progress_token
3389 .clone()
3390 .unwrap();
3391
3392 let language = Arc::new(Language::new(
3393 LanguageConfig {
3394 name: "Rust".into(),
3395 path_suffixes: vec!["rs".to_string()],
3396 language_server: Some(language_server_config),
3397 ..Default::default()
3398 },
3399 Some(tree_sitter_rust::language()),
3400 ));
3401
3402 let fs = FakeFs::new(cx.background());
3403 fs.insert_tree(
3404 "/dir",
3405 json!({
3406 "a.rs": "fn a() { A }",
3407 "b.rs": "const y: i32 = 1",
3408 }),
3409 )
3410 .await;
3411
3412 let project = Project::test(fs, &mut cx);
3413 project.update(&mut cx, |project, _| {
3414 Arc::get_mut(&mut project.languages).unwrap().add(language);
3415 });
3416
3417 let (tree, _) = project
3418 .update(&mut cx, |project, cx| {
3419 project.find_or_create_local_worktree("/dir", false, cx)
3420 })
3421 .await
3422 .unwrap();
3423 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
3424
3425 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3426 .await;
3427
3428 // Cause worktree to start the fake language server
3429 let _buffer = project
3430 .update(&mut cx, |project, cx| {
3431 project.open_buffer((worktree_id, Path::new("b.rs")), cx)
3432 })
3433 .await
3434 .unwrap();
3435
3436 let mut events = subscribe(&project, &mut cx);
3437
3438 let mut fake_server = fake_servers.next().await.unwrap();
3439 fake_server.start_progress(&progress_token).await;
3440 assert_eq!(
3441 events.next().await.unwrap(),
3442 Event::DiskBasedDiagnosticsStarted
3443 );
3444
3445 fake_server.start_progress(&progress_token).await;
3446 fake_server.end_progress(&progress_token).await;
3447 fake_server.start_progress(&progress_token).await;
3448
3449 fake_server
3450 .notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
3451 uri: Url::from_file_path("/dir/a.rs").unwrap(),
3452 version: None,
3453 diagnostics: vec![lsp::Diagnostic {
3454 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
3455 severity: Some(lsp::DiagnosticSeverity::ERROR),
3456 message: "undefined variable 'A'".to_string(),
3457 ..Default::default()
3458 }],
3459 })
3460 .await;
3461 assert_eq!(
3462 events.next().await.unwrap(),
3463 Event::DiagnosticsUpdated((worktree_id, Path::new("a.rs")).into())
3464 );
3465
3466 fake_server.end_progress(&progress_token).await;
3467 fake_server.end_progress(&progress_token).await;
3468 assert_eq!(
3469 events.next().await.unwrap(),
3470 Event::DiskBasedDiagnosticsUpdated
3471 );
3472 assert_eq!(
3473 events.next().await.unwrap(),
3474 Event::DiskBasedDiagnosticsFinished
3475 );
3476
3477 let buffer = project
3478 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
3479 .await
3480 .unwrap();
3481
3482 buffer.read_with(&cx, |buffer, _| {
3483 let snapshot = buffer.snapshot();
3484 let diagnostics = snapshot
3485 .diagnostics_in_range::<_, Point>(0..buffer.len())
3486 .collect::<Vec<_>>();
3487 assert_eq!(
3488 diagnostics,
3489 &[DiagnosticEntry {
3490 range: Point::new(0, 9)..Point::new(0, 10),
3491 diagnostic: Diagnostic {
3492 severity: lsp::DiagnosticSeverity::ERROR,
3493 message: "undefined variable 'A'".to_string(),
3494 group_id: 0,
3495 is_primary: true,
3496 ..Default::default()
3497 }
3498 }]
3499 )
3500 });
3501 }
3502
3503 #[gpui::test]
3504 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
3505 let dir = temp_tree(json!({
3506 "root": {
3507 "dir1": {},
3508 "dir2": {
3509 "dir3": {}
3510 }
3511 }
3512 }));
3513
3514 let project = Project::test(Arc::new(RealFs), &mut cx);
3515 let (tree, _) = project
3516 .update(&mut cx, |project, cx| {
3517 project.find_or_create_local_worktree(&dir.path(), false, cx)
3518 })
3519 .await
3520 .unwrap();
3521
3522 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3523 .await;
3524
3525 let cancel_flag = Default::default();
3526 let results = project
3527 .read_with(&cx, |project, cx| {
3528 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
3529 })
3530 .await;
3531
3532 assert!(results.is_empty());
3533 }
3534
3535 #[gpui::test]
3536 async fn test_definition(mut cx: gpui::TestAppContext) {
3537 let (language_server_config, mut fake_servers) = LanguageServerConfig::fake();
3538 let language = Arc::new(Language::new(
3539 LanguageConfig {
3540 name: "Rust".into(),
3541 path_suffixes: vec!["rs".to_string()],
3542 language_server: Some(language_server_config),
3543 ..Default::default()
3544 },
3545 Some(tree_sitter_rust::language()),
3546 ));
3547
3548 let fs = FakeFs::new(cx.background());
3549 fs.insert_tree(
3550 "/dir",
3551 json!({
3552 "a.rs": "const fn a() { A }",
3553 "b.rs": "const y: i32 = crate::a()",
3554 }),
3555 )
3556 .await;
3557
3558 let project = Project::test(fs, &mut cx);
3559 project.update(&mut cx, |project, _| {
3560 Arc::get_mut(&mut project.languages).unwrap().add(language);
3561 });
3562
3563 let (tree, _) = project
3564 .update(&mut cx, |project, cx| {
3565 project.find_or_create_local_worktree("/dir/b.rs", false, cx)
3566 })
3567 .await
3568 .unwrap();
3569 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
3570 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3571 .await;
3572
3573 let buffer = project
3574 .update(&mut cx, |project, cx| {
3575 project.open_buffer(
3576 ProjectPath {
3577 worktree_id,
3578 path: Path::new("").into(),
3579 },
3580 cx,
3581 )
3582 })
3583 .await
3584 .unwrap();
3585
3586 let mut fake_server = fake_servers.next().await.unwrap();
3587 fake_server.handle_request::<lsp::request::GotoDefinition, _>(move |params| {
3588 let params = params.text_document_position_params;
3589 assert_eq!(
3590 params.text_document.uri.to_file_path().unwrap(),
3591 Path::new("/dir/b.rs"),
3592 );
3593 assert_eq!(params.position, lsp::Position::new(0, 22));
3594
3595 Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
3596 lsp::Url::from_file_path("/dir/a.rs").unwrap(),
3597 lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
3598 )))
3599 });
3600
3601 let mut definitions = project
3602 .update(&mut cx, |project, cx| project.definition(&buffer, 22, cx))
3603 .await
3604 .unwrap();
3605
3606 assert_eq!(definitions.len(), 1);
3607 let definition = definitions.pop().unwrap();
3608 cx.update(|cx| {
3609 let target_buffer = definition.target_buffer.read(cx);
3610 assert_eq!(
3611 target_buffer
3612 .file()
3613 .unwrap()
3614 .as_local()
3615 .unwrap()
3616 .abs_path(cx),
3617 Path::new("/dir/a.rs"),
3618 );
3619 assert_eq!(definition.target_range.to_offset(target_buffer), 9..10);
3620 assert_eq!(
3621 list_worktrees(&project, cx),
3622 [("/dir/b.rs".as_ref(), false), ("/dir/a.rs".as_ref(), true)]
3623 );
3624
3625 drop(definition);
3626 });
3627 cx.read(|cx| {
3628 assert_eq!(
3629 list_worktrees(&project, cx),
3630 [("/dir/b.rs".as_ref(), false)]
3631 );
3632 });
3633
3634 fn list_worktrees<'a>(
3635 project: &'a ModelHandle<Project>,
3636 cx: &'a AppContext,
3637 ) -> Vec<(&'a Path, bool)> {
3638 project
3639 .read(cx)
3640 .worktrees(cx)
3641 .map(|worktree| {
3642 let worktree = worktree.read(cx);
3643 (
3644 worktree.as_local().unwrap().abs_path().as_ref(),
3645 worktree.is_weak(),
3646 )
3647 })
3648 .collect::<Vec<_>>()
3649 }
3650 }
3651
3652 #[gpui::test]
3653 async fn test_save_file(mut cx: gpui::TestAppContext) {
3654 let fs = FakeFs::new(cx.background());
3655 fs.insert_tree(
3656 "/dir",
3657 json!({
3658 "file1": "the old contents",
3659 }),
3660 )
3661 .await;
3662
3663 let project = Project::test(fs.clone(), &mut cx);
3664 let worktree_id = project
3665 .update(&mut cx, |p, cx| {
3666 p.find_or_create_local_worktree("/dir", false, cx)
3667 })
3668 .await
3669 .unwrap()
3670 .0
3671 .read_with(&cx, |tree, _| tree.id());
3672
3673 let buffer = project
3674 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
3675 .await
3676 .unwrap();
3677 buffer
3678 .update(&mut cx, |buffer, cx| {
3679 assert_eq!(buffer.text(), "the old contents");
3680 buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
3681 buffer.save(cx)
3682 })
3683 .await
3684 .unwrap();
3685
3686 let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
3687 assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
3688 }
3689
3690 #[gpui::test]
3691 async fn test_save_in_single_file_worktree(mut cx: gpui::TestAppContext) {
3692 let fs = FakeFs::new(cx.background());
3693 fs.insert_tree(
3694 "/dir",
3695 json!({
3696 "file1": "the old contents",
3697 }),
3698 )
3699 .await;
3700
3701 let project = Project::test(fs.clone(), &mut cx);
3702 let worktree_id = project
3703 .update(&mut cx, |p, cx| {
3704 p.find_or_create_local_worktree("/dir/file1", false, cx)
3705 })
3706 .await
3707 .unwrap()
3708 .0
3709 .read_with(&cx, |tree, _| tree.id());
3710
3711 let buffer = project
3712 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, ""), cx))
3713 .await
3714 .unwrap();
3715 buffer
3716 .update(&mut cx, |buffer, cx| {
3717 buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
3718 buffer.save(cx)
3719 })
3720 .await
3721 .unwrap();
3722
3723 let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
3724 assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
3725 }
3726
3727 #[gpui::test(retries = 5)]
3728 async fn test_rescan_and_remote_updates(mut cx: gpui::TestAppContext) {
3729 let dir = temp_tree(json!({
3730 "a": {
3731 "file1": "",
3732 "file2": "",
3733 "file3": "",
3734 },
3735 "b": {
3736 "c": {
3737 "file4": "",
3738 "file5": "",
3739 }
3740 }
3741 }));
3742
3743 let project = Project::test(Arc::new(RealFs), &mut cx);
3744 let rpc = project.read_with(&cx, |p, _| p.client.clone());
3745
3746 let (tree, _) = project
3747 .update(&mut cx, |p, cx| {
3748 p.find_or_create_local_worktree(dir.path(), false, cx)
3749 })
3750 .await
3751 .unwrap();
3752 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
3753
3754 let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
3755 let buffer = project.update(cx, |p, cx| p.open_buffer((worktree_id, path), cx));
3756 async move { buffer.await.unwrap() }
3757 };
3758 let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
3759 tree.read_with(cx, |tree, _| {
3760 tree.entry_for_path(path)
3761 .expect(&format!("no entry for path {}", path))
3762 .id
3763 })
3764 };
3765
3766 let buffer2 = buffer_for_path("a/file2", &mut cx).await;
3767 let buffer3 = buffer_for_path("a/file3", &mut cx).await;
3768 let buffer4 = buffer_for_path("b/c/file4", &mut cx).await;
3769 let buffer5 = buffer_for_path("b/c/file5", &mut cx).await;
3770
3771 let file2_id = id_for_path("a/file2", &cx);
3772 let file3_id = id_for_path("a/file3", &cx);
3773 let file4_id = id_for_path("b/c/file4", &cx);
3774
3775 // Wait for the initial scan.
3776 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3777 .await;
3778
3779 // Create a remote copy of this worktree.
3780 let initial_snapshot = tree.read_with(&cx, |tree, _| tree.as_local().unwrap().snapshot());
3781 let (remote, load_task) = cx.update(|cx| {
3782 Worktree::remote(
3783 1,
3784 1,
3785 initial_snapshot.to_proto(&Default::default(), Default::default()),
3786 rpc.clone(),
3787 cx,
3788 )
3789 });
3790 load_task.await;
3791
3792 cx.read(|cx| {
3793 assert!(!buffer2.read(cx).is_dirty());
3794 assert!(!buffer3.read(cx).is_dirty());
3795 assert!(!buffer4.read(cx).is_dirty());
3796 assert!(!buffer5.read(cx).is_dirty());
3797 });
3798
3799 // Rename and delete files and directories.
3800 tree.flush_fs_events(&cx).await;
3801 std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
3802 std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
3803 std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
3804 std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
3805 tree.flush_fs_events(&cx).await;
3806
3807 let expected_paths = vec![
3808 "a",
3809 "a/file1",
3810 "a/file2.new",
3811 "b",
3812 "d",
3813 "d/file3",
3814 "d/file4",
3815 ];
3816
3817 cx.read(|app| {
3818 assert_eq!(
3819 tree.read(app)
3820 .paths()
3821 .map(|p| p.to_str().unwrap())
3822 .collect::<Vec<_>>(),
3823 expected_paths
3824 );
3825
3826 assert_eq!(id_for_path("a/file2.new", &cx), file2_id);
3827 assert_eq!(id_for_path("d/file3", &cx), file3_id);
3828 assert_eq!(id_for_path("d/file4", &cx), file4_id);
3829
3830 assert_eq!(
3831 buffer2.read(app).file().unwrap().path().as_ref(),
3832 Path::new("a/file2.new")
3833 );
3834 assert_eq!(
3835 buffer3.read(app).file().unwrap().path().as_ref(),
3836 Path::new("d/file3")
3837 );
3838 assert_eq!(
3839 buffer4.read(app).file().unwrap().path().as_ref(),
3840 Path::new("d/file4")
3841 );
3842 assert_eq!(
3843 buffer5.read(app).file().unwrap().path().as_ref(),
3844 Path::new("b/c/file5")
3845 );
3846
3847 assert!(!buffer2.read(app).file().unwrap().is_deleted());
3848 assert!(!buffer3.read(app).file().unwrap().is_deleted());
3849 assert!(!buffer4.read(app).file().unwrap().is_deleted());
3850 assert!(buffer5.read(app).file().unwrap().is_deleted());
3851 });
3852
3853 // Update the remote worktree. Check that it becomes consistent with the
3854 // local worktree.
3855 remote.update(&mut cx, |remote, cx| {
3856 let update_message = tree.read(cx).as_local().unwrap().snapshot().build_update(
3857 &initial_snapshot,
3858 1,
3859 1,
3860 0,
3861 true,
3862 );
3863 remote
3864 .as_remote_mut()
3865 .unwrap()
3866 .snapshot
3867 .apply_remote_update(update_message)
3868 .unwrap();
3869
3870 assert_eq!(
3871 remote
3872 .paths()
3873 .map(|p| p.to_str().unwrap())
3874 .collect::<Vec<_>>(),
3875 expected_paths
3876 );
3877 });
3878 }
3879
3880 #[gpui::test]
3881 async fn test_buffer_deduping(mut cx: gpui::TestAppContext) {
3882 let fs = FakeFs::new(cx.background());
3883 fs.insert_tree(
3884 "/the-dir",
3885 json!({
3886 "a.txt": "a-contents",
3887 "b.txt": "b-contents",
3888 }),
3889 )
3890 .await;
3891
3892 let project = Project::test(fs.clone(), &mut cx);
3893 let worktree_id = project
3894 .update(&mut cx, |p, cx| {
3895 p.find_or_create_local_worktree("/the-dir", false, cx)
3896 })
3897 .await
3898 .unwrap()
3899 .0
3900 .read_with(&cx, |tree, _| tree.id());
3901
3902 // Spawn multiple tasks to open paths, repeating some paths.
3903 let (buffer_a_1, buffer_b, buffer_a_2) = project.update(&mut cx, |p, cx| {
3904 (
3905 p.open_buffer((worktree_id, "a.txt"), cx),
3906 p.open_buffer((worktree_id, "b.txt"), cx),
3907 p.open_buffer((worktree_id, "a.txt"), cx),
3908 )
3909 });
3910
3911 let buffer_a_1 = buffer_a_1.await.unwrap();
3912 let buffer_a_2 = buffer_a_2.await.unwrap();
3913 let buffer_b = buffer_b.await.unwrap();
3914 assert_eq!(buffer_a_1.read_with(&cx, |b, _| b.text()), "a-contents");
3915 assert_eq!(buffer_b.read_with(&cx, |b, _| b.text()), "b-contents");
3916
3917 // There is only one buffer per path.
3918 let buffer_a_id = buffer_a_1.id();
3919 assert_eq!(buffer_a_2.id(), buffer_a_id);
3920
3921 // Open the same path again while it is still open.
3922 drop(buffer_a_1);
3923 let buffer_a_3 = project
3924 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3925 .await
3926 .unwrap();
3927
3928 // There's still only one buffer per path.
3929 assert_eq!(buffer_a_3.id(), buffer_a_id);
3930 }
3931
3932 #[gpui::test]
3933 async fn test_buffer_is_dirty(mut cx: gpui::TestAppContext) {
3934 use std::fs;
3935
3936 let dir = temp_tree(json!({
3937 "file1": "abc",
3938 "file2": "def",
3939 "file3": "ghi",
3940 }));
3941
3942 let project = Project::test(Arc::new(RealFs), &mut cx);
3943 let (worktree, _) = project
3944 .update(&mut cx, |p, cx| {
3945 p.find_or_create_local_worktree(dir.path(), false, cx)
3946 })
3947 .await
3948 .unwrap();
3949 let worktree_id = worktree.read_with(&cx, |worktree, _| worktree.id());
3950
3951 worktree.flush_fs_events(&cx).await;
3952 worktree
3953 .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete())
3954 .await;
3955
3956 let buffer1 = project
3957 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
3958 .await
3959 .unwrap();
3960 let events = Rc::new(RefCell::new(Vec::new()));
3961
3962 // initially, the buffer isn't dirty.
3963 buffer1.update(&mut cx, |buffer, cx| {
3964 cx.subscribe(&buffer1, {
3965 let events = events.clone();
3966 move |_, _, event, _| events.borrow_mut().push(event.clone())
3967 })
3968 .detach();
3969
3970 assert!(!buffer.is_dirty());
3971 assert!(events.borrow().is_empty());
3972
3973 buffer.edit(vec![1..2], "", cx);
3974 });
3975
3976 // after the first edit, the buffer is dirty, and emits a dirtied event.
3977 buffer1.update(&mut cx, |buffer, cx| {
3978 assert!(buffer.text() == "ac");
3979 assert!(buffer.is_dirty());
3980 assert_eq!(
3981 *events.borrow(),
3982 &[language::Event::Edited, language::Event::Dirtied]
3983 );
3984 events.borrow_mut().clear();
3985 buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
3986 });
3987
3988 // after saving, the buffer is not dirty, and emits a saved event.
3989 buffer1.update(&mut cx, |buffer, cx| {
3990 assert!(!buffer.is_dirty());
3991 assert_eq!(*events.borrow(), &[language::Event::Saved]);
3992 events.borrow_mut().clear();
3993
3994 buffer.edit(vec![1..1], "B", cx);
3995 buffer.edit(vec![2..2], "D", cx);
3996 });
3997
3998 // after editing again, the buffer is dirty, and emits another dirty event.
3999 buffer1.update(&mut cx, |buffer, cx| {
4000 assert!(buffer.text() == "aBDc");
4001 assert!(buffer.is_dirty());
4002 assert_eq!(
4003 *events.borrow(),
4004 &[
4005 language::Event::Edited,
4006 language::Event::Dirtied,
4007 language::Event::Edited,
4008 ],
4009 );
4010 events.borrow_mut().clear();
4011
4012 // TODO - currently, after restoring the buffer to its
4013 // previously-saved state, the is still considered dirty.
4014 buffer.edit([1..3], "", cx);
4015 assert!(buffer.text() == "ac");
4016 assert!(buffer.is_dirty());
4017 });
4018
4019 assert_eq!(*events.borrow(), &[language::Event::Edited]);
4020
4021 // When a file is deleted, the buffer is considered dirty.
4022 let events = Rc::new(RefCell::new(Vec::new()));
4023 let buffer2 = project
4024 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file2"), cx))
4025 .await
4026 .unwrap();
4027 buffer2.update(&mut cx, |_, cx| {
4028 cx.subscribe(&buffer2, {
4029 let events = events.clone();
4030 move |_, _, event, _| events.borrow_mut().push(event.clone())
4031 })
4032 .detach();
4033 });
4034
4035 fs::remove_file(dir.path().join("file2")).unwrap();
4036 buffer2.condition(&cx, |b, _| b.is_dirty()).await;
4037 assert_eq!(
4038 *events.borrow(),
4039 &[language::Event::Dirtied, language::Event::FileHandleChanged]
4040 );
4041
4042 // When a file is already dirty when deleted, we don't emit a Dirtied event.
4043 let events = Rc::new(RefCell::new(Vec::new()));
4044 let buffer3 = project
4045 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file3"), cx))
4046 .await
4047 .unwrap();
4048 buffer3.update(&mut cx, |_, cx| {
4049 cx.subscribe(&buffer3, {
4050 let events = events.clone();
4051 move |_, _, event, _| events.borrow_mut().push(event.clone())
4052 })
4053 .detach();
4054 });
4055
4056 worktree.flush_fs_events(&cx).await;
4057 buffer3.update(&mut cx, |buffer, cx| {
4058 buffer.edit(Some(0..0), "x", cx);
4059 });
4060 events.borrow_mut().clear();
4061 fs::remove_file(dir.path().join("file3")).unwrap();
4062 buffer3
4063 .condition(&cx, |_, _| !events.borrow().is_empty())
4064 .await;
4065 assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]);
4066 cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
4067 }
4068
4069 #[gpui::test]
4070 async fn test_buffer_file_changes_on_disk(mut cx: gpui::TestAppContext) {
4071 use std::fs;
4072
4073 let initial_contents = "aaa\nbbbbb\nc\n";
4074 let dir = temp_tree(json!({ "the-file": initial_contents }));
4075
4076 let project = Project::test(Arc::new(RealFs), &mut cx);
4077 let (worktree, _) = project
4078 .update(&mut cx, |p, cx| {
4079 p.find_or_create_local_worktree(dir.path(), false, cx)
4080 })
4081 .await
4082 .unwrap();
4083 let worktree_id = worktree.read_with(&cx, |tree, _| tree.id());
4084
4085 worktree
4086 .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete())
4087 .await;
4088
4089 let abs_path = dir.path().join("the-file");
4090 let buffer = project
4091 .update(&mut cx, |p, cx| {
4092 p.open_buffer((worktree_id, "the-file"), cx)
4093 })
4094 .await
4095 .unwrap();
4096
4097 // TODO
4098 // Add a cursor on each row.
4099 // let selection_set_id = buffer.update(&mut cx, |buffer, cx| {
4100 // assert!(!buffer.is_dirty());
4101 // buffer.add_selection_set(
4102 // &(0..3)
4103 // .map(|row| Selection {
4104 // id: row as usize,
4105 // start: Point::new(row, 1),
4106 // end: Point::new(row, 1),
4107 // reversed: false,
4108 // goal: SelectionGoal::None,
4109 // })
4110 // .collect::<Vec<_>>(),
4111 // cx,
4112 // )
4113 // });
4114
4115 // Change the file on disk, adding two new lines of text, and removing
4116 // one line.
4117 buffer.read_with(&cx, |buffer, _| {
4118 assert!(!buffer.is_dirty());
4119 assert!(!buffer.has_conflict());
4120 });
4121 let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
4122 fs::write(&abs_path, new_contents).unwrap();
4123
4124 // Because the buffer was not modified, it is reloaded from disk. Its
4125 // contents are edited according to the diff between the old and new
4126 // file contents.
4127 buffer
4128 .condition(&cx, |buffer, _| buffer.text() == new_contents)
4129 .await;
4130
4131 buffer.update(&mut cx, |buffer, _| {
4132 assert_eq!(buffer.text(), new_contents);
4133 assert!(!buffer.is_dirty());
4134 assert!(!buffer.has_conflict());
4135
4136 // TODO
4137 // let cursor_positions = buffer
4138 // .selection_set(selection_set_id)
4139 // .unwrap()
4140 // .selections::<Point>(&*buffer)
4141 // .map(|selection| {
4142 // assert_eq!(selection.start, selection.end);
4143 // selection.start
4144 // })
4145 // .collect::<Vec<_>>();
4146 // assert_eq!(
4147 // cursor_positions,
4148 // [Point::new(1, 1), Point::new(3, 1), Point::new(4, 0)]
4149 // );
4150 });
4151
4152 // Modify the buffer
4153 buffer.update(&mut cx, |buffer, cx| {
4154 buffer.edit(vec![0..0], " ", cx);
4155 assert!(buffer.is_dirty());
4156 assert!(!buffer.has_conflict());
4157 });
4158
4159 // Change the file on disk again, adding blank lines to the beginning.
4160 fs::write(&abs_path, "\n\n\nAAAA\naaa\nBB\nbbbbb\n").unwrap();
4161
4162 // Because the buffer is modified, it doesn't reload from disk, but is
4163 // marked as having a conflict.
4164 buffer
4165 .condition(&cx, |buffer, _| buffer.has_conflict())
4166 .await;
4167 }
4168
4169 #[gpui::test]
4170 async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
4171 let fs = FakeFs::new(cx.background());
4172 fs.insert_tree(
4173 "/the-dir",
4174 json!({
4175 "a.rs": "
4176 fn foo(mut v: Vec<usize>) {
4177 for x in &v {
4178 v.push(1);
4179 }
4180 }
4181 "
4182 .unindent(),
4183 }),
4184 )
4185 .await;
4186
4187 let project = Project::test(fs.clone(), &mut cx);
4188 let (worktree, _) = project
4189 .update(&mut cx, |p, cx| {
4190 p.find_or_create_local_worktree("/the-dir", false, cx)
4191 })
4192 .await
4193 .unwrap();
4194 let worktree_id = worktree.read_with(&cx, |tree, _| tree.id());
4195
4196 let buffer = project
4197 .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4198 .await
4199 .unwrap();
4200
4201 let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
4202 let message = lsp::PublishDiagnosticsParams {
4203 uri: buffer_uri.clone(),
4204 diagnostics: vec![
4205 lsp::Diagnostic {
4206 range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
4207 severity: Some(DiagnosticSeverity::WARNING),
4208 message: "error 1".to_string(),
4209 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
4210 location: lsp::Location {
4211 uri: buffer_uri.clone(),
4212 range: lsp::Range::new(
4213 lsp::Position::new(1, 8),
4214 lsp::Position::new(1, 9),
4215 ),
4216 },
4217 message: "error 1 hint 1".to_string(),
4218 }]),
4219 ..Default::default()
4220 },
4221 lsp::Diagnostic {
4222 range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
4223 severity: Some(DiagnosticSeverity::HINT),
4224 message: "error 1 hint 1".to_string(),
4225 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
4226 location: lsp::Location {
4227 uri: buffer_uri.clone(),
4228 range: lsp::Range::new(
4229 lsp::Position::new(1, 8),
4230 lsp::Position::new(1, 9),
4231 ),
4232 },
4233 message: "original diagnostic".to_string(),
4234 }]),
4235 ..Default::default()
4236 },
4237 lsp::Diagnostic {
4238 range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
4239 severity: Some(DiagnosticSeverity::ERROR),
4240 message: "error 2".to_string(),
4241 related_information: Some(vec![
4242 lsp::DiagnosticRelatedInformation {
4243 location: lsp::Location {
4244 uri: buffer_uri.clone(),
4245 range: lsp::Range::new(
4246 lsp::Position::new(1, 13),
4247 lsp::Position::new(1, 15),
4248 ),
4249 },
4250 message: "error 2 hint 1".to_string(),
4251 },
4252 lsp::DiagnosticRelatedInformation {
4253 location: lsp::Location {
4254 uri: buffer_uri.clone(),
4255 range: lsp::Range::new(
4256 lsp::Position::new(1, 13),
4257 lsp::Position::new(1, 15),
4258 ),
4259 },
4260 message: "error 2 hint 2".to_string(),
4261 },
4262 ]),
4263 ..Default::default()
4264 },
4265 lsp::Diagnostic {
4266 range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
4267 severity: Some(DiagnosticSeverity::HINT),
4268 message: "error 2 hint 1".to_string(),
4269 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
4270 location: lsp::Location {
4271 uri: buffer_uri.clone(),
4272 range: lsp::Range::new(
4273 lsp::Position::new(2, 8),
4274 lsp::Position::new(2, 17),
4275 ),
4276 },
4277 message: "original diagnostic".to_string(),
4278 }]),
4279 ..Default::default()
4280 },
4281 lsp::Diagnostic {
4282 range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
4283 severity: Some(DiagnosticSeverity::HINT),
4284 message: "error 2 hint 2".to_string(),
4285 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
4286 location: lsp::Location {
4287 uri: buffer_uri.clone(),
4288 range: lsp::Range::new(
4289 lsp::Position::new(2, 8),
4290 lsp::Position::new(2, 17),
4291 ),
4292 },
4293 message: "original diagnostic".to_string(),
4294 }]),
4295 ..Default::default()
4296 },
4297 ],
4298 version: None,
4299 };
4300
4301 project
4302 .update(&mut cx, |p, cx| {
4303 p.update_diagnostics(message, &Default::default(), cx)
4304 })
4305 .unwrap();
4306 let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot());
4307
4308 assert_eq!(
4309 buffer
4310 .diagnostics_in_range::<_, Point>(0..buffer.len())
4311 .collect::<Vec<_>>(),
4312 &[
4313 DiagnosticEntry {
4314 range: Point::new(1, 8)..Point::new(1, 9),
4315 diagnostic: Diagnostic {
4316 severity: DiagnosticSeverity::WARNING,
4317 message: "error 1".to_string(),
4318 group_id: 0,
4319 is_primary: true,
4320 ..Default::default()
4321 }
4322 },
4323 DiagnosticEntry {
4324 range: Point::new(1, 8)..Point::new(1, 9),
4325 diagnostic: Diagnostic {
4326 severity: DiagnosticSeverity::HINT,
4327 message: "error 1 hint 1".to_string(),
4328 group_id: 0,
4329 is_primary: false,
4330 ..Default::default()
4331 }
4332 },
4333 DiagnosticEntry {
4334 range: Point::new(1, 13)..Point::new(1, 15),
4335 diagnostic: Diagnostic {
4336 severity: DiagnosticSeverity::HINT,
4337 message: "error 2 hint 1".to_string(),
4338 group_id: 1,
4339 is_primary: false,
4340 ..Default::default()
4341 }
4342 },
4343 DiagnosticEntry {
4344 range: Point::new(1, 13)..Point::new(1, 15),
4345 diagnostic: Diagnostic {
4346 severity: DiagnosticSeverity::HINT,
4347 message: "error 2 hint 2".to_string(),
4348 group_id: 1,
4349 is_primary: false,
4350 ..Default::default()
4351 }
4352 },
4353 DiagnosticEntry {
4354 range: Point::new(2, 8)..Point::new(2, 17),
4355 diagnostic: Diagnostic {
4356 severity: DiagnosticSeverity::ERROR,
4357 message: "error 2".to_string(),
4358 group_id: 1,
4359 is_primary: true,
4360 ..Default::default()
4361 }
4362 }
4363 ]
4364 );
4365
4366 assert_eq!(
4367 buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
4368 &[
4369 DiagnosticEntry {
4370 range: Point::new(1, 8)..Point::new(1, 9),
4371 diagnostic: Diagnostic {
4372 severity: DiagnosticSeverity::WARNING,
4373 message: "error 1".to_string(),
4374 group_id: 0,
4375 is_primary: true,
4376 ..Default::default()
4377 }
4378 },
4379 DiagnosticEntry {
4380 range: Point::new(1, 8)..Point::new(1, 9),
4381 diagnostic: Diagnostic {
4382 severity: DiagnosticSeverity::HINT,
4383 message: "error 1 hint 1".to_string(),
4384 group_id: 0,
4385 is_primary: false,
4386 ..Default::default()
4387 }
4388 },
4389 ]
4390 );
4391 assert_eq!(
4392 buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
4393 &[
4394 DiagnosticEntry {
4395 range: Point::new(1, 13)..Point::new(1, 15),
4396 diagnostic: Diagnostic {
4397 severity: DiagnosticSeverity::HINT,
4398 message: "error 2 hint 1".to_string(),
4399 group_id: 1,
4400 is_primary: false,
4401 ..Default::default()
4402 }
4403 },
4404 DiagnosticEntry {
4405 range: Point::new(1, 13)..Point::new(1, 15),
4406 diagnostic: Diagnostic {
4407 severity: DiagnosticSeverity::HINT,
4408 message: "error 2 hint 2".to_string(),
4409 group_id: 1,
4410 is_primary: false,
4411 ..Default::default()
4412 }
4413 },
4414 DiagnosticEntry {
4415 range: Point::new(2, 8)..Point::new(2, 17),
4416 diagnostic: Diagnostic {
4417 severity: DiagnosticSeverity::ERROR,
4418 message: "error 2".to_string(),
4419 group_id: 1,
4420 is_primary: true,
4421 ..Default::default()
4422 }
4423 }
4424 ]
4425 );
4426 }
4427
4428 #[gpui::test]
4429 async fn test_rename(mut cx: gpui::TestAppContext) {
4430 let (language_server_config, mut fake_servers) = LanguageServerConfig::fake();
4431 let language = Arc::new(Language::new(
4432 LanguageConfig {
4433 name: "Rust".into(),
4434 path_suffixes: vec!["rs".to_string()],
4435 language_server: Some(language_server_config),
4436 ..Default::default()
4437 },
4438 Some(tree_sitter_rust::language()),
4439 ));
4440
4441 let fs = FakeFs::new(cx.background());
4442 fs.insert_tree(
4443 "/dir",
4444 json!({
4445 "one.rs": "const ONE: usize = 1;",
4446 "two.rs": "const TWO: usize = one::ONE + one::ONE;"
4447 }),
4448 )
4449 .await;
4450
4451 let project = Project::test(fs.clone(), &mut cx);
4452 project.update(&mut cx, |project, _| {
4453 Arc::get_mut(&mut project.languages).unwrap().add(language);
4454 });
4455
4456 let (tree, _) = project
4457 .update(&mut cx, |project, cx| {
4458 project.find_or_create_local_worktree("/dir", false, cx)
4459 })
4460 .await
4461 .unwrap();
4462 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
4463 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
4464 .await;
4465
4466 let buffer = project
4467 .update(&mut cx, |project, cx| {
4468 project.open_buffer((worktree_id, Path::new("one.rs")), cx)
4469 })
4470 .await
4471 .unwrap();
4472
4473 let mut fake_server = fake_servers.next().await.unwrap();
4474
4475 let response = project.update(&mut cx, |project, cx| {
4476 project.prepare_rename(buffer.clone(), 7, cx)
4477 });
4478 fake_server
4479 .handle_request::<lsp::request::PrepareRenameRequest, _>(|params| {
4480 assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
4481 assert_eq!(params.position, lsp::Position::new(0, 7));
4482 Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
4483 lsp::Position::new(0, 6),
4484 lsp::Position::new(0, 9),
4485 )))
4486 })
4487 .next()
4488 .await
4489 .unwrap();
4490 let range = response.await.unwrap().unwrap();
4491 let range = buffer.read_with(&cx, |buffer, _| range.to_offset(buffer));
4492 assert_eq!(range, 6..9);
4493
4494 let response = project.update(&mut cx, |project, cx| {
4495 project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
4496 });
4497 fake_server
4498 .handle_request::<lsp::request::Rename, _>(|params| {
4499 assert_eq!(
4500 params.text_document_position.text_document.uri.as_str(),
4501 "file:///dir/one.rs"
4502 );
4503 assert_eq!(
4504 params.text_document_position.position,
4505 lsp::Position::new(0, 7)
4506 );
4507 assert_eq!(params.new_name, "THREE");
4508 Some(lsp::WorkspaceEdit {
4509 changes: Some(
4510 [
4511 (
4512 lsp::Url::from_file_path("/dir/one.rs").unwrap(),
4513 vec![lsp::TextEdit::new(
4514 lsp::Range::new(
4515 lsp::Position::new(0, 6),
4516 lsp::Position::new(0, 9),
4517 ),
4518 "THREE".to_string(),
4519 )],
4520 ),
4521 (
4522 lsp::Url::from_file_path("/dir/two.rs").unwrap(),
4523 vec![
4524 lsp::TextEdit::new(
4525 lsp::Range::new(
4526 lsp::Position::new(0, 24),
4527 lsp::Position::new(0, 27),
4528 ),
4529 "THREE".to_string(),
4530 ),
4531 lsp::TextEdit::new(
4532 lsp::Range::new(
4533 lsp::Position::new(0, 35),
4534 lsp::Position::new(0, 38),
4535 ),
4536 "THREE".to_string(),
4537 ),
4538 ],
4539 ),
4540 ]
4541 .into_iter()
4542 .collect(),
4543 ),
4544 ..Default::default()
4545 })
4546 })
4547 .next()
4548 .await
4549 .unwrap();
4550 let mut transaction = response.await.unwrap().0;
4551 assert_eq!(transaction.len(), 2);
4552 assert_eq!(
4553 transaction
4554 .remove_entry(&buffer)
4555 .unwrap()
4556 .0
4557 .read_with(&cx, |buffer, _| buffer.text()),
4558 "const THREE: usize = 1;"
4559 );
4560 assert_eq!(
4561 transaction
4562 .into_keys()
4563 .next()
4564 .unwrap()
4565 .read_with(&cx, |buffer, _| buffer.text()),
4566 "const TWO: usize = one::THREE + one::THREE;"
4567 );
4568 }
4569}