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