1mod db;
2pub mod fs;
3mod ignore;
4mod lsp_command;
5pub mod search;
6pub mod worktree;
7
8use anyhow::{anyhow, Context, Result};
9use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
10use clock::ReplicaId;
11use collections::{hash_map, BTreeMap, HashMap, HashSet};
12use futures::{future::Shared, Future, FutureExt, StreamExt, TryFutureExt};
13use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
14use gpui::{
15 AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
16 MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle,
17};
18use language::{
19 point_to_lsp,
20 proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
21 range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CodeAction, CodeLabel, Completion,
22 Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, Language,
23 LanguageRegistry, LanguageServerName, LocalFile, LspAdapter, OffsetRangeExt, Operation, Patch,
24 PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
25};
26use lsp::{
27 DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString,
28 MarkedString,
29};
30use lsp_command::*;
31use parking_lot::Mutex;
32use postage::stream::Stream;
33use postage::watch;
34use rand::prelude::*;
35use search::SearchQuery;
36use serde::Serialize;
37use settings::Settings;
38use sha2::{Digest, Sha256};
39use similar::{ChangeTag, TextDiff};
40use std::{
41 cell::RefCell,
42 cmp::{self, Ordering},
43 convert::TryInto,
44 ffi::OsString,
45 hash::Hash,
46 mem,
47 ops::Range,
48 os::unix::{ffi::OsStrExt, prelude::OsStringExt},
49 path::{Component, Path, PathBuf},
50 rc::Rc,
51 sync::{
52 atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
53 Arc,
54 },
55 time::Instant,
56};
57use thiserror::Error;
58use util::{post_inc, ResultExt, TryFutureExt as _};
59
60pub use db::Db;
61pub use fs::*;
62pub use worktree::*;
63
64pub trait Item: Entity {
65 fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
66}
67
68pub struct ProjectStore {
69 db: Arc<Db>,
70 projects: Vec<WeakModelHandle<Project>>,
71}
72
73pub struct Project {
74 worktrees: Vec<WorktreeHandle>,
75 active_entry: Option<ProjectEntryId>,
76 languages: Arc<LanguageRegistry>,
77 language_servers:
78 HashMap<(WorktreeId, LanguageServerName), (Arc<dyn LspAdapter>, Arc<LanguageServer>)>,
79 started_language_servers:
80 HashMap<(WorktreeId, LanguageServerName), Task<Option<Arc<LanguageServer>>>>,
81 language_server_statuses: BTreeMap<usize, LanguageServerStatus>,
82 language_server_settings: Arc<Mutex<serde_json::Value>>,
83 last_workspace_edits_by_language_server: HashMap<usize, ProjectTransaction>,
84 next_language_server_id: usize,
85 client: Arc<client::Client>,
86 next_entry_id: Arc<AtomicUsize>,
87 next_diagnostic_group_id: usize,
88 user_store: ModelHandle<UserStore>,
89 project_store: ModelHandle<ProjectStore>,
90 fs: Arc<dyn Fs>,
91 client_state: ProjectClientState,
92 collaborators: HashMap<PeerId, Collaborator>,
93 subscriptions: Vec<client::Subscription>,
94 opened_buffer: (Rc<RefCell<watch::Sender<()>>>, watch::Receiver<()>),
95 shared_buffers: HashMap<PeerId, HashSet<u64>>,
96 loading_buffers: HashMap<
97 ProjectPath,
98 postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
99 >,
100 loading_local_worktrees:
101 HashMap<Arc<Path>, Shared<Task<Result<ModelHandle<Worktree>, Arc<anyhow::Error>>>>>,
102 opened_buffers: HashMap<u64, OpenBuffer>,
103 buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>,
104 nonce: u128,
105 initialized_persistent_state: bool,
106}
107
108#[derive(Error, Debug)]
109pub enum JoinProjectError {
110 #[error("host declined join request")]
111 HostDeclined,
112 #[error("host closed the project")]
113 HostClosedProject,
114 #[error("host went offline")]
115 HostWentOffline,
116 #[error("{0}")]
117 Other(#[from] anyhow::Error),
118}
119
120enum OpenBuffer {
121 Strong(ModelHandle<Buffer>),
122 Weak(WeakModelHandle<Buffer>),
123 Loading(Vec<Operation>),
124}
125
126enum WorktreeHandle {
127 Strong(ModelHandle<Worktree>),
128 Weak(WeakModelHandle<Worktree>),
129}
130
131enum ProjectClientState {
132 Local {
133 is_shared: bool,
134 remote_id_tx: watch::Sender<Option<u64>>,
135 remote_id_rx: watch::Receiver<Option<u64>>,
136 online_tx: watch::Sender<bool>,
137 online_rx: watch::Receiver<bool>,
138 _maintain_remote_id_task: Task<Option<()>>,
139 },
140 Remote {
141 sharing_has_stopped: bool,
142 remote_id: u64,
143 replica_id: ReplicaId,
144 _detect_unshare_task: Task<Option<()>>,
145 },
146}
147
148#[derive(Clone, Debug)]
149pub struct Collaborator {
150 pub user: Arc<User>,
151 pub peer_id: PeerId,
152 pub replica_id: ReplicaId,
153}
154
155#[derive(Clone, Debug, PartialEq, Eq)]
156pub enum Event {
157 ActiveEntryChanged(Option<ProjectEntryId>),
158 WorktreeAdded,
159 WorktreeRemoved(WorktreeId),
160 DiskBasedDiagnosticsStarted {
161 language_server_id: usize,
162 },
163 DiskBasedDiagnosticsFinished {
164 language_server_id: usize,
165 },
166 DiagnosticsUpdated {
167 path: ProjectPath,
168 language_server_id: usize,
169 },
170 RemoteIdChanged(Option<u64>),
171 CollaboratorLeft(PeerId),
172 ContactRequestedJoin(Arc<User>),
173 ContactCancelledJoinRequest(Arc<User>),
174}
175
176#[derive(Serialize)]
177pub struct LanguageServerStatus {
178 pub name: String,
179 pub pending_work: BTreeMap<String, LanguageServerProgress>,
180 pub pending_diagnostic_updates: isize,
181}
182
183#[derive(Clone, Debug, Serialize)]
184pub struct LanguageServerProgress {
185 pub message: Option<String>,
186 pub percentage: Option<usize>,
187 #[serde(skip_serializing)]
188 pub last_update_at: Instant,
189}
190
191#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
192pub struct ProjectPath {
193 pub worktree_id: WorktreeId,
194 pub path: Arc<Path>,
195}
196
197#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)]
198pub struct DiagnosticSummary {
199 pub language_server_id: usize,
200 pub error_count: usize,
201 pub warning_count: usize,
202}
203
204#[derive(Debug)]
205pub struct Location {
206 pub buffer: ModelHandle<Buffer>,
207 pub range: Range<language::Anchor>,
208}
209
210#[derive(Debug)]
211pub struct DocumentHighlight {
212 pub range: Range<language::Anchor>,
213 pub kind: DocumentHighlightKind,
214}
215
216#[derive(Clone, Debug)]
217pub struct Symbol {
218 pub source_worktree_id: WorktreeId,
219 pub worktree_id: WorktreeId,
220 pub language_server_name: LanguageServerName,
221 pub path: PathBuf,
222 pub label: CodeLabel,
223 pub name: String,
224 pub kind: lsp::SymbolKind,
225 pub range: Range<PointUtf16>,
226 pub signature: [u8; 32],
227}
228
229#[derive(Clone, Debug, PartialEq)]
230pub struct HoverBlock {
231 pub text: String,
232 pub language: Option<String>,
233}
234
235impl HoverBlock {
236 fn try_new(marked_string: MarkedString) -> Option<Self> {
237 let result = match marked_string {
238 MarkedString::LanguageString(LanguageString { language, value }) => HoverBlock {
239 text: value,
240 language: Some(language),
241 },
242 MarkedString::String(text) => HoverBlock {
243 text,
244 language: None,
245 },
246 };
247 if result.text.is_empty() {
248 None
249 } else {
250 Some(result)
251 }
252 }
253}
254
255#[derive(Debug)]
256pub struct Hover {
257 pub contents: Vec<HoverBlock>,
258 pub range: Option<Range<language::Anchor>>,
259}
260
261#[derive(Default)]
262pub struct ProjectTransaction(pub HashMap<ModelHandle<Buffer>, language::Transaction>);
263
264impl DiagnosticSummary {
265 fn new<'a, T: 'a>(
266 language_server_id: usize,
267 diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>,
268 ) -> Self {
269 let mut this = Self {
270 language_server_id,
271 error_count: 0,
272 warning_count: 0,
273 };
274
275 for entry in diagnostics {
276 if entry.diagnostic.is_primary {
277 match entry.diagnostic.severity {
278 DiagnosticSeverity::ERROR => this.error_count += 1,
279 DiagnosticSeverity::WARNING => this.warning_count += 1,
280 _ => {}
281 }
282 }
283 }
284
285 this
286 }
287
288 pub fn is_empty(&self) -> bool {
289 self.error_count == 0 && self.warning_count == 0
290 }
291
292 pub fn to_proto(&self, path: &Path) -> proto::DiagnosticSummary {
293 proto::DiagnosticSummary {
294 path: path.to_string_lossy().to_string(),
295 language_server_id: self.language_server_id as u64,
296 error_count: self.error_count as u32,
297 warning_count: self.warning_count as u32,
298 }
299 }
300}
301
302#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
303pub struct ProjectEntryId(usize);
304
305impl ProjectEntryId {
306 pub const MAX: Self = Self(usize::MAX);
307
308 pub fn new(counter: &AtomicUsize) -> Self {
309 Self(counter.fetch_add(1, SeqCst))
310 }
311
312 pub fn from_proto(id: u64) -> Self {
313 Self(id as usize)
314 }
315
316 pub fn to_proto(&self) -> u64 {
317 self.0 as u64
318 }
319
320 pub fn to_usize(&self) -> usize {
321 self.0
322 }
323}
324
325impl Project {
326 pub fn init(client: &Arc<Client>) {
327 client.add_model_message_handler(Self::handle_request_join_project);
328 client.add_model_message_handler(Self::handle_add_collaborator);
329 client.add_model_message_handler(Self::handle_buffer_reloaded);
330 client.add_model_message_handler(Self::handle_buffer_saved);
331 client.add_model_message_handler(Self::handle_start_language_server);
332 client.add_model_message_handler(Self::handle_update_language_server);
333 client.add_model_message_handler(Self::handle_remove_collaborator);
334 client.add_model_message_handler(Self::handle_join_project_request_cancelled);
335 client.add_model_message_handler(Self::handle_update_project);
336 client.add_model_message_handler(Self::handle_unregister_project);
337 client.add_model_message_handler(Self::handle_project_unshared);
338 client.add_model_message_handler(Self::handle_update_buffer_file);
339 client.add_model_message_handler(Self::handle_update_buffer);
340 client.add_model_message_handler(Self::handle_update_diagnostic_summary);
341 client.add_model_message_handler(Self::handle_update_worktree);
342 client.add_model_request_handler(Self::handle_create_project_entry);
343 client.add_model_request_handler(Self::handle_rename_project_entry);
344 client.add_model_request_handler(Self::handle_copy_project_entry);
345 client.add_model_request_handler(Self::handle_delete_project_entry);
346 client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
347 client.add_model_request_handler(Self::handle_apply_code_action);
348 client.add_model_request_handler(Self::handle_reload_buffers);
349 client.add_model_request_handler(Self::handle_format_buffers);
350 client.add_model_request_handler(Self::handle_get_code_actions);
351 client.add_model_request_handler(Self::handle_get_completions);
352 client.add_model_request_handler(Self::handle_lsp_command::<GetHover>);
353 client.add_model_request_handler(Self::handle_lsp_command::<GetDefinition>);
354 client.add_model_request_handler(Self::handle_lsp_command::<GetDocumentHighlights>);
355 client.add_model_request_handler(Self::handle_lsp_command::<GetReferences>);
356 client.add_model_request_handler(Self::handle_lsp_command::<PrepareRename>);
357 client.add_model_request_handler(Self::handle_lsp_command::<PerformRename>);
358 client.add_model_request_handler(Self::handle_search_project);
359 client.add_model_request_handler(Self::handle_get_project_symbols);
360 client.add_model_request_handler(Self::handle_open_buffer_for_symbol);
361 client.add_model_request_handler(Self::handle_open_buffer_by_id);
362 client.add_model_request_handler(Self::handle_open_buffer_by_path);
363 client.add_model_request_handler(Self::handle_save_buffer);
364 }
365
366 pub fn local(
367 online: bool,
368 client: Arc<Client>,
369 user_store: ModelHandle<UserStore>,
370 project_store: ModelHandle<ProjectStore>,
371 languages: Arc<LanguageRegistry>,
372 fs: Arc<dyn Fs>,
373 cx: &mut MutableAppContext,
374 ) -> ModelHandle<Self> {
375 cx.add_model(|cx: &mut ModelContext<Self>| {
376 let (online_tx, online_rx) = watch::channel_with(online);
377 let (remote_id_tx, remote_id_rx) = watch::channel();
378 let _maintain_remote_id_task = cx.spawn_weak({
379 let status_rx = client.clone().status();
380 let online_rx = online_rx.clone();
381 move |this, mut cx| async move {
382 let mut stream = Stream::map(status_rx.clone(), drop)
383 .merge(Stream::map(online_rx.clone(), drop));
384 while stream.recv().await.is_some() {
385 let this = this.upgrade(&cx)?;
386 if status_rx.borrow().is_connected() && *online_rx.borrow() {
387 this.update(&mut cx, |this, cx| this.register(cx))
388 .await
389 .log_err()?;
390 } else {
391 this.update(&mut cx, |this, cx| this.unregister(cx))
392 .await
393 .log_err();
394 }
395 }
396 None
397 }
398 });
399
400 let handle = cx.weak_handle();
401 project_store.update(cx, |store, cx| store.add_project(handle, cx));
402
403 let (opened_buffer_tx, opened_buffer_rx) = watch::channel();
404 Self {
405 worktrees: Default::default(),
406 collaborators: Default::default(),
407 opened_buffers: Default::default(),
408 shared_buffers: Default::default(),
409 loading_buffers: Default::default(),
410 loading_local_worktrees: Default::default(),
411 buffer_snapshots: Default::default(),
412 client_state: ProjectClientState::Local {
413 is_shared: false,
414 remote_id_tx,
415 remote_id_rx,
416 online_tx,
417 online_rx,
418 _maintain_remote_id_task,
419 },
420 opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx),
421 subscriptions: Vec::new(),
422 active_entry: None,
423 languages,
424 client,
425 user_store,
426 project_store,
427 fs,
428 next_entry_id: Default::default(),
429 next_diagnostic_group_id: Default::default(),
430 language_servers: Default::default(),
431 started_language_servers: Default::default(),
432 language_server_statuses: Default::default(),
433 last_workspace_edits_by_language_server: Default::default(),
434 language_server_settings: Default::default(),
435 next_language_server_id: 0,
436 nonce: StdRng::from_entropy().gen(),
437 initialized_persistent_state: false,
438 }
439 })
440 }
441
442 pub async fn remote(
443 remote_id: u64,
444 client: Arc<Client>,
445 user_store: ModelHandle<UserStore>,
446 project_store: ModelHandle<ProjectStore>,
447 languages: Arc<LanguageRegistry>,
448 fs: Arc<dyn Fs>,
449 mut cx: AsyncAppContext,
450 ) -> Result<ModelHandle<Self>, JoinProjectError> {
451 client.authenticate_and_connect(true, &cx).await?;
452
453 let response = client
454 .request(proto::JoinProject {
455 project_id: remote_id,
456 })
457 .await?;
458
459 let response = match response.variant.ok_or_else(|| anyhow!("missing variant"))? {
460 proto::join_project_response::Variant::Accept(response) => response,
461 proto::join_project_response::Variant::Decline(decline) => {
462 match proto::join_project_response::decline::Reason::from_i32(decline.reason) {
463 Some(proto::join_project_response::decline::Reason::Declined) => {
464 Err(JoinProjectError::HostDeclined)?
465 }
466 Some(proto::join_project_response::decline::Reason::Closed) => {
467 Err(JoinProjectError::HostClosedProject)?
468 }
469 Some(proto::join_project_response::decline::Reason::WentOffline) => {
470 Err(JoinProjectError::HostWentOffline)?
471 }
472 None => Err(anyhow!("missing decline reason"))?,
473 }
474 }
475 };
476
477 let replica_id = response.replica_id as ReplicaId;
478
479 let mut worktrees = Vec::new();
480 for worktree in response.worktrees {
481 let (worktree, load_task) = cx
482 .update(|cx| Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx));
483 worktrees.push(worktree);
484 load_task.detach();
485 }
486
487 let (opened_buffer_tx, opened_buffer_rx) = watch::channel();
488 let this = cx.add_model(|cx: &mut ModelContext<Self>| {
489 let handle = cx.weak_handle();
490 project_store.update(cx, |store, cx| store.add_project(handle, cx));
491
492 let mut this = Self {
493 worktrees: Vec::new(),
494 loading_buffers: Default::default(),
495 opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx),
496 shared_buffers: Default::default(),
497 loading_local_worktrees: Default::default(),
498 active_entry: None,
499 collaborators: Default::default(),
500 languages,
501 user_store: user_store.clone(),
502 project_store,
503 fs,
504 next_entry_id: Default::default(),
505 next_diagnostic_group_id: Default::default(),
506 subscriptions: vec![client.add_model_for_remote_entity(remote_id, cx)],
507 client: client.clone(),
508 client_state: ProjectClientState::Remote {
509 sharing_has_stopped: false,
510 remote_id,
511 replica_id,
512 _detect_unshare_task: cx.spawn_weak(move |this, mut cx| {
513 async move {
514 let mut status = client.status();
515 let is_connected =
516 status.next().await.map_or(false, |s| s.is_connected());
517 // Even if we're initially connected, any future change of the status means we momentarily disconnected.
518 if !is_connected || status.next().await.is_some() {
519 if let Some(this) = this.upgrade(&cx) {
520 this.update(&mut cx, |this, cx| this.removed_from_project(cx))
521 }
522 }
523 Ok(())
524 }
525 .log_err()
526 }),
527 },
528 language_servers: Default::default(),
529 started_language_servers: Default::default(),
530 language_server_settings: Default::default(),
531 language_server_statuses: response
532 .language_servers
533 .into_iter()
534 .map(|server| {
535 (
536 server.id as usize,
537 LanguageServerStatus {
538 name: server.name,
539 pending_work: Default::default(),
540 pending_diagnostic_updates: 0,
541 },
542 )
543 })
544 .collect(),
545 last_workspace_edits_by_language_server: Default::default(),
546 next_language_server_id: 0,
547 opened_buffers: Default::default(),
548 buffer_snapshots: Default::default(),
549 nonce: StdRng::from_entropy().gen(),
550 initialized_persistent_state: false,
551 };
552 for worktree in worktrees {
553 this.add_worktree(&worktree, cx);
554 }
555 this
556 });
557
558 let user_ids = response
559 .collaborators
560 .iter()
561 .map(|peer| peer.user_id)
562 .collect();
563 user_store
564 .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))
565 .await?;
566 let mut collaborators = HashMap::default();
567 for message in response.collaborators {
568 let collaborator = Collaborator::from_proto(message, &user_store, &mut cx).await?;
569 collaborators.insert(collaborator.peer_id, collaborator);
570 }
571
572 this.update(&mut cx, |this, _| {
573 this.collaborators = collaborators;
574 });
575
576 Ok(this)
577 }
578
579 #[cfg(any(test, feature = "test-support"))]
580 pub async fn test(
581 fs: Arc<dyn Fs>,
582 root_paths: impl IntoIterator<Item = &Path>,
583 cx: &mut gpui::TestAppContext,
584 ) -> ModelHandle<Project> {
585 let languages = Arc::new(LanguageRegistry::test());
586 let http_client = client::test::FakeHttpClient::with_404_response();
587 let client = client::Client::new(http_client.clone());
588 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
589 let project_store = cx.add_model(|_| ProjectStore::new(Db::open_fake()));
590 let project = cx.update(|cx| {
591 Project::local(true, client, user_store, project_store, languages, fs, cx)
592 });
593 for path in root_paths {
594 let (tree, _) = project
595 .update(cx, |project, cx| {
596 project.find_or_create_local_worktree(path, true, cx)
597 })
598 .await
599 .unwrap();
600 tree.read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
601 .await;
602 }
603 project
604 }
605
606 pub fn restore_state(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
607 if self.is_remote() {
608 return Task::ready(Ok(()));
609 }
610
611 let db = self.project_store.read(cx).db.clone();
612 let keys = self.db_keys_for_online_state(cx);
613 let online_by_default = cx.global::<Settings>().projects_online_by_default;
614 let read_online = cx.background().spawn(async move {
615 let values = db.read(keys)?;
616 anyhow::Ok(
617 values
618 .into_iter()
619 .all(|e| e.map_or(online_by_default, |e| e == [true as u8])),
620 )
621 });
622 cx.spawn(|this, mut cx| async move {
623 let online = read_online.await.log_err().unwrap_or(false);
624 this.update(&mut cx, |this, cx| {
625 this.initialized_persistent_state = true;
626 if let ProjectClientState::Local { online_tx, .. } = &mut this.client_state {
627 let mut online_tx = online_tx.borrow_mut();
628 if *online_tx != online {
629 *online_tx = online;
630 drop(online_tx);
631 this.metadata_changed(false, cx);
632 }
633 }
634 });
635 Ok(())
636 })
637 }
638
639 fn persist_state(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
640 if self.is_remote() || !self.initialized_persistent_state {
641 return Task::ready(Ok(()));
642 }
643
644 let db = self.project_store.read(cx).db.clone();
645 let keys = self.db_keys_for_online_state(cx);
646 let is_online = self.is_online();
647 cx.background().spawn(async move {
648 let value = &[is_online as u8];
649 db.write(keys.into_iter().map(|key| (key, value)))
650 })
651 }
652
653 pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option<ModelHandle<Buffer>> {
654 self.opened_buffers
655 .get(&remote_id)
656 .and_then(|buffer| buffer.upgrade(cx))
657 }
658
659 pub fn languages(&self) -> &Arc<LanguageRegistry> {
660 &self.languages
661 }
662
663 pub fn client(&self) -> Arc<Client> {
664 self.client.clone()
665 }
666
667 pub fn user_store(&self) -> ModelHandle<UserStore> {
668 self.user_store.clone()
669 }
670
671 pub fn project_store(&self) -> ModelHandle<ProjectStore> {
672 self.project_store.clone()
673 }
674
675 #[cfg(any(test, feature = "test-support"))]
676 pub fn check_invariants(&self, cx: &AppContext) {
677 if self.is_local() {
678 let mut worktree_root_paths = HashMap::default();
679 for worktree in self.worktrees(cx) {
680 let worktree = worktree.read(cx);
681 let abs_path = worktree.as_local().unwrap().abs_path().clone();
682 let prev_worktree_id = worktree_root_paths.insert(abs_path.clone(), worktree.id());
683 assert_eq!(
684 prev_worktree_id,
685 None,
686 "abs path {:?} for worktree {:?} is not unique ({:?} was already registered with the same path)",
687 abs_path,
688 worktree.id(),
689 prev_worktree_id
690 )
691 }
692 } else {
693 let replica_id = self.replica_id();
694 for buffer in self.opened_buffers.values() {
695 if let Some(buffer) = buffer.upgrade(cx) {
696 let buffer = buffer.read(cx);
697 assert_eq!(
698 buffer.deferred_ops_len(),
699 0,
700 "replica {}, buffer {} has deferred operations",
701 replica_id,
702 buffer.remote_id()
703 );
704 }
705 }
706 }
707 }
708
709 #[cfg(any(test, feature = "test-support"))]
710 pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &AppContext) -> bool {
711 let path = path.into();
712 if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
713 self.opened_buffers.iter().any(|(_, buffer)| {
714 if let Some(buffer) = buffer.upgrade(cx) {
715 if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
716 if file.worktree == worktree && file.path() == &path.path {
717 return true;
718 }
719 }
720 }
721 false
722 })
723 } else {
724 false
725 }
726 }
727
728 pub fn fs(&self) -> &Arc<dyn Fs> {
729 &self.fs
730 }
731
732 pub fn set_online(&mut self, online: bool, cx: &mut ModelContext<Self>) {
733 if let ProjectClientState::Local { online_tx, .. } = &mut self.client_state {
734 let mut online_tx = online_tx.borrow_mut();
735 if *online_tx != online {
736 *online_tx = online;
737 drop(online_tx);
738 self.metadata_changed(true, cx);
739 }
740 }
741 }
742
743 pub fn is_online(&self) -> bool {
744 match &self.client_state {
745 ProjectClientState::Local { online_rx, .. } => *online_rx.borrow(),
746 ProjectClientState::Remote { .. } => true,
747 }
748 }
749
750 fn unregister(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
751 self.unshared(cx);
752 if let ProjectClientState::Local { remote_id_rx, .. } = &mut self.client_state {
753 if let Some(remote_id) = *remote_id_rx.borrow() {
754 let request = self.client.request(proto::UnregisterProject {
755 project_id: remote_id,
756 });
757 return cx.spawn(|this, mut cx| async move {
758 let response = request.await;
759
760 // Unregistering the project causes the server to send out a
761 // contact update removing this project from the host's list
762 // of online projects. Wait until this contact update has been
763 // processed before clearing out this project's remote id, so
764 // that there is no moment where this project appears in the
765 // contact metadata and *also* has no remote id.
766 this.update(&mut cx, |this, cx| {
767 this.user_store()
768 .update(cx, |store, _| store.contact_updates_done())
769 })
770 .await;
771
772 this.update(&mut cx, |this, cx| {
773 if let ProjectClientState::Local { remote_id_tx, .. } =
774 &mut this.client_state
775 {
776 *remote_id_tx.borrow_mut() = None;
777 }
778 this.subscriptions.clear();
779 this.metadata_changed(false, cx);
780 });
781 response.map(drop)
782 });
783 }
784 }
785 Task::ready(Ok(()))
786 }
787
788 fn register(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
789 if let ProjectClientState::Local { remote_id_rx, .. } = &self.client_state {
790 if remote_id_rx.borrow().is_some() {
791 return Task::ready(Ok(()));
792 }
793 }
794
795 let response = self.client.request(proto::RegisterProject {});
796 cx.spawn(|this, mut cx| async move {
797 let remote_id = response.await?.project_id;
798 this.update(&mut cx, |this, cx| {
799 if let ProjectClientState::Local { remote_id_tx, .. } = &mut this.client_state {
800 *remote_id_tx.borrow_mut() = Some(remote_id);
801 }
802
803 this.metadata_changed(false, cx);
804 cx.emit(Event::RemoteIdChanged(Some(remote_id)));
805 this.subscriptions
806 .push(this.client.add_model_for_remote_entity(remote_id, cx));
807 Ok(())
808 })
809 })
810 }
811
812 pub fn remote_id(&self) -> Option<u64> {
813 match &self.client_state {
814 ProjectClientState::Local { remote_id_rx, .. } => *remote_id_rx.borrow(),
815 ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
816 }
817 }
818
819 pub fn next_remote_id(&self) -> impl Future<Output = u64> {
820 let mut id = None;
821 let mut watch = None;
822 match &self.client_state {
823 ProjectClientState::Local { remote_id_rx, .. } => watch = Some(remote_id_rx.clone()),
824 ProjectClientState::Remote { remote_id, .. } => id = Some(*remote_id),
825 }
826
827 async move {
828 if let Some(id) = id {
829 return id;
830 }
831 let mut watch = watch.unwrap();
832 loop {
833 let id = *watch.borrow();
834 if let Some(id) = id {
835 return id;
836 }
837 watch.next().await;
838 }
839 }
840 }
841
842 pub fn shared_remote_id(&self) -> Option<u64> {
843 match &self.client_state {
844 ProjectClientState::Local {
845 remote_id_rx,
846 is_shared,
847 ..
848 } => {
849 if *is_shared {
850 *remote_id_rx.borrow()
851 } else {
852 None
853 }
854 }
855 ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
856 }
857 }
858
859 pub fn replica_id(&self) -> ReplicaId {
860 match &self.client_state {
861 ProjectClientState::Local { .. } => 0,
862 ProjectClientState::Remote { replica_id, .. } => *replica_id,
863 }
864 }
865
866 fn metadata_changed(&mut self, persist: bool, cx: &mut ModelContext<Self>) {
867 if let ProjectClientState::Local {
868 remote_id_rx,
869 online_rx,
870 ..
871 } = &self.client_state
872 {
873 if let (Some(project_id), true) = (*remote_id_rx.borrow(), *online_rx.borrow()) {
874 self.client
875 .send(proto::UpdateProject {
876 project_id,
877 worktrees: self
878 .worktrees
879 .iter()
880 .filter_map(|worktree| {
881 worktree.upgrade(&cx).map(|worktree| {
882 worktree.read(cx).as_local().unwrap().metadata_proto()
883 })
884 })
885 .collect(),
886 })
887 .log_err();
888 }
889
890 self.project_store.update(cx, |_, cx| cx.notify());
891 if persist {
892 self.persist_state(cx).detach_and_log_err(cx);
893 }
894 cx.notify();
895 }
896 }
897
898 pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
899 &self.collaborators
900 }
901
902 pub fn worktrees<'a>(
903 &'a self,
904 cx: &'a AppContext,
905 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
906 self.worktrees
907 .iter()
908 .filter_map(move |worktree| worktree.upgrade(cx))
909 }
910
911 pub fn visible_worktrees<'a>(
912 &'a self,
913 cx: &'a AppContext,
914 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
915 self.worktrees.iter().filter_map(|worktree| {
916 worktree.upgrade(cx).and_then(|worktree| {
917 if worktree.read(cx).is_visible() {
918 Some(worktree)
919 } else {
920 None
921 }
922 })
923 })
924 }
925
926 pub fn worktree_root_names<'a>(&'a self, cx: &'a AppContext) -> impl Iterator<Item = &'a str> {
927 self.visible_worktrees(cx)
928 .map(|tree| tree.read(cx).root_name())
929 }
930
931 fn db_keys_for_online_state(&self, cx: &AppContext) -> Vec<String> {
932 self.worktrees
933 .iter()
934 .filter_map(|worktree| {
935 let worktree = worktree.upgrade(&cx)?.read(cx);
936 if worktree.is_visible() {
937 Some(format!(
938 "project-path-online:{}",
939 worktree.as_local().unwrap().abs_path().to_string_lossy()
940 ))
941 } else {
942 None
943 }
944 })
945 .collect::<Vec<_>>()
946 }
947
948 pub fn worktree_for_id(
949 &self,
950 id: WorktreeId,
951 cx: &AppContext,
952 ) -> Option<ModelHandle<Worktree>> {
953 self.worktrees(cx)
954 .find(|worktree| worktree.read(cx).id() == id)
955 }
956
957 pub fn worktree_for_entry(
958 &self,
959 entry_id: ProjectEntryId,
960 cx: &AppContext,
961 ) -> Option<ModelHandle<Worktree>> {
962 self.worktrees(cx)
963 .find(|worktree| worktree.read(cx).contains_entry(entry_id))
964 }
965
966 pub fn worktree_id_for_entry(
967 &self,
968 entry_id: ProjectEntryId,
969 cx: &AppContext,
970 ) -> Option<WorktreeId> {
971 self.worktree_for_entry(entry_id, cx)
972 .map(|worktree| worktree.read(cx).id())
973 }
974
975 pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
976 paths.iter().all(|path| self.contains_path(&path, cx))
977 }
978
979 pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
980 for worktree in self.worktrees(cx) {
981 let worktree = worktree.read(cx).as_local();
982 if worktree.map_or(false, |w| w.contains_abs_path(path)) {
983 return true;
984 }
985 }
986 false
987 }
988
989 pub fn create_entry(
990 &mut self,
991 project_path: impl Into<ProjectPath>,
992 is_directory: bool,
993 cx: &mut ModelContext<Self>,
994 ) -> Option<Task<Result<Entry>>> {
995 let project_path = project_path.into();
996 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
997 if self.is_local() {
998 Some(worktree.update(cx, |worktree, cx| {
999 worktree
1000 .as_local_mut()
1001 .unwrap()
1002 .create_entry(project_path.path, is_directory, cx)
1003 }))
1004 } else {
1005 let client = self.client.clone();
1006 let project_id = self.remote_id().unwrap();
1007 Some(cx.spawn_weak(|_, mut cx| async move {
1008 let response = client
1009 .request(proto::CreateProjectEntry {
1010 worktree_id: project_path.worktree_id.to_proto(),
1011 project_id,
1012 path: project_path.path.as_os_str().as_bytes().to_vec(),
1013 is_directory,
1014 })
1015 .await?;
1016 let entry = response
1017 .entry
1018 .ok_or_else(|| anyhow!("missing entry in response"))?;
1019 worktree
1020 .update(&mut cx, |worktree, cx| {
1021 worktree.as_remote().unwrap().insert_entry(
1022 entry,
1023 response.worktree_scan_id as usize,
1024 cx,
1025 )
1026 })
1027 .await
1028 }))
1029 }
1030 }
1031
1032 pub fn copy_entry(
1033 &mut self,
1034 entry_id: ProjectEntryId,
1035 new_path: impl Into<Arc<Path>>,
1036 cx: &mut ModelContext<Self>,
1037 ) -> Option<Task<Result<Entry>>> {
1038 let worktree = self.worktree_for_entry(entry_id, cx)?;
1039 let new_path = new_path.into();
1040 if self.is_local() {
1041 worktree.update(cx, |worktree, cx| {
1042 worktree
1043 .as_local_mut()
1044 .unwrap()
1045 .copy_entry(entry_id, new_path, cx)
1046 })
1047 } else {
1048 let client = self.client.clone();
1049 let project_id = self.remote_id().unwrap();
1050
1051 Some(cx.spawn_weak(|_, mut cx| async move {
1052 let response = client
1053 .request(proto::CopyProjectEntry {
1054 project_id,
1055 entry_id: entry_id.to_proto(),
1056 new_path: new_path.as_os_str().as_bytes().to_vec(),
1057 })
1058 .await?;
1059 let entry = response
1060 .entry
1061 .ok_or_else(|| anyhow!("missing entry in response"))?;
1062 worktree
1063 .update(&mut cx, |worktree, cx| {
1064 worktree.as_remote().unwrap().insert_entry(
1065 entry,
1066 response.worktree_scan_id as usize,
1067 cx,
1068 )
1069 })
1070 .await
1071 }))
1072 }
1073 }
1074
1075 pub fn rename_entry(
1076 &mut self,
1077 entry_id: ProjectEntryId,
1078 new_path: impl Into<Arc<Path>>,
1079 cx: &mut ModelContext<Self>,
1080 ) -> Option<Task<Result<Entry>>> {
1081 let worktree = self.worktree_for_entry(entry_id, cx)?;
1082 let new_path = new_path.into();
1083 if self.is_local() {
1084 worktree.update(cx, |worktree, cx| {
1085 worktree
1086 .as_local_mut()
1087 .unwrap()
1088 .rename_entry(entry_id, new_path, cx)
1089 })
1090 } else {
1091 let client = self.client.clone();
1092 let project_id = self.remote_id().unwrap();
1093
1094 Some(cx.spawn_weak(|_, mut cx| async move {
1095 let response = client
1096 .request(proto::RenameProjectEntry {
1097 project_id,
1098 entry_id: entry_id.to_proto(),
1099 new_path: new_path.as_os_str().as_bytes().to_vec(),
1100 })
1101 .await?;
1102 let entry = response
1103 .entry
1104 .ok_or_else(|| anyhow!("missing entry in response"))?;
1105 worktree
1106 .update(&mut cx, |worktree, cx| {
1107 worktree.as_remote().unwrap().insert_entry(
1108 entry,
1109 response.worktree_scan_id as usize,
1110 cx,
1111 )
1112 })
1113 .await
1114 }))
1115 }
1116 }
1117
1118 pub fn delete_entry(
1119 &mut self,
1120 entry_id: ProjectEntryId,
1121 cx: &mut ModelContext<Self>,
1122 ) -> Option<Task<Result<()>>> {
1123 let worktree = self.worktree_for_entry(entry_id, cx)?;
1124 if self.is_local() {
1125 worktree.update(cx, |worktree, cx| {
1126 worktree.as_local_mut().unwrap().delete_entry(entry_id, cx)
1127 })
1128 } else {
1129 let client = self.client.clone();
1130 let project_id = self.remote_id().unwrap();
1131 Some(cx.spawn_weak(|_, mut cx| async move {
1132 let response = client
1133 .request(proto::DeleteProjectEntry {
1134 project_id,
1135 entry_id: entry_id.to_proto(),
1136 })
1137 .await?;
1138 worktree
1139 .update(&mut cx, move |worktree, cx| {
1140 worktree.as_remote().unwrap().delete_entry(
1141 entry_id,
1142 response.worktree_scan_id as usize,
1143 cx,
1144 )
1145 })
1146 .await
1147 }))
1148 }
1149 }
1150
1151 fn share(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
1152 let project_id;
1153 if let ProjectClientState::Local {
1154 remote_id_rx,
1155 is_shared,
1156 ..
1157 } = &mut self.client_state
1158 {
1159 if *is_shared {
1160 return Task::ready(Ok(()));
1161 }
1162 *is_shared = true;
1163 if let Some(id) = *remote_id_rx.borrow() {
1164 project_id = id;
1165 } else {
1166 return Task::ready(Err(anyhow!("project hasn't been registered")));
1167 }
1168 } else {
1169 return Task::ready(Err(anyhow!("can't share a remote project")));
1170 };
1171
1172 for open_buffer in self.opened_buffers.values_mut() {
1173 match open_buffer {
1174 OpenBuffer::Strong(_) => {}
1175 OpenBuffer::Weak(buffer) => {
1176 if let Some(buffer) = buffer.upgrade(cx) {
1177 *open_buffer = OpenBuffer::Strong(buffer);
1178 }
1179 }
1180 OpenBuffer::Loading(_) => unreachable!(),
1181 }
1182 }
1183
1184 for worktree_handle in self.worktrees.iter_mut() {
1185 match worktree_handle {
1186 WorktreeHandle::Strong(_) => {}
1187 WorktreeHandle::Weak(worktree) => {
1188 if let Some(worktree) = worktree.upgrade(cx) {
1189 *worktree_handle = WorktreeHandle::Strong(worktree);
1190 }
1191 }
1192 }
1193 }
1194
1195 let mut tasks = Vec::new();
1196 for worktree in self.worktrees(cx).collect::<Vec<_>>() {
1197 worktree.update(cx, |worktree, cx| {
1198 let worktree = worktree.as_local_mut().unwrap();
1199 tasks.push(worktree.share(project_id, cx));
1200 });
1201 }
1202
1203 cx.spawn(|this, mut cx| async move {
1204 for task in tasks {
1205 task.await?;
1206 }
1207 this.update(&mut cx, |_, cx| cx.notify());
1208 Ok(())
1209 })
1210 }
1211
1212 fn unshared(&mut self, cx: &mut ModelContext<Self>) {
1213 if let ProjectClientState::Local { is_shared, .. } = &mut self.client_state {
1214 if !*is_shared {
1215 return;
1216 }
1217
1218 *is_shared = false;
1219 self.collaborators.clear();
1220 self.shared_buffers.clear();
1221 for worktree_handle in self.worktrees.iter_mut() {
1222 if let WorktreeHandle::Strong(worktree) = worktree_handle {
1223 let is_visible = worktree.update(cx, |worktree, _| {
1224 worktree.as_local_mut().unwrap().unshare();
1225 worktree.is_visible()
1226 });
1227 if !is_visible {
1228 *worktree_handle = WorktreeHandle::Weak(worktree.downgrade());
1229 }
1230 }
1231 }
1232
1233 for open_buffer in self.opened_buffers.values_mut() {
1234 match open_buffer {
1235 OpenBuffer::Strong(buffer) => {
1236 *open_buffer = OpenBuffer::Weak(buffer.downgrade());
1237 }
1238 _ => {}
1239 }
1240 }
1241
1242 cx.notify();
1243 } else {
1244 log::error!("attempted to unshare a remote project");
1245 }
1246 }
1247
1248 pub fn respond_to_join_request(
1249 &mut self,
1250 requester_id: u64,
1251 allow: bool,
1252 cx: &mut ModelContext<Self>,
1253 ) {
1254 if let Some(project_id) = self.remote_id() {
1255 let share = self.share(cx);
1256 let client = self.client.clone();
1257 cx.foreground()
1258 .spawn(async move {
1259 share.await?;
1260 client.send(proto::RespondToJoinProjectRequest {
1261 requester_id,
1262 project_id,
1263 allow,
1264 })
1265 })
1266 .detach_and_log_err(cx);
1267 }
1268 }
1269
1270 fn removed_from_project(&mut self, cx: &mut ModelContext<Self>) {
1271 if let ProjectClientState::Remote {
1272 sharing_has_stopped,
1273 ..
1274 } = &mut self.client_state
1275 {
1276 *sharing_has_stopped = true;
1277 self.collaborators.clear();
1278 cx.notify();
1279 }
1280 }
1281
1282 pub fn is_read_only(&self) -> bool {
1283 match &self.client_state {
1284 ProjectClientState::Local { .. } => false,
1285 ProjectClientState::Remote {
1286 sharing_has_stopped,
1287 ..
1288 } => *sharing_has_stopped,
1289 }
1290 }
1291
1292 pub fn is_local(&self) -> bool {
1293 match &self.client_state {
1294 ProjectClientState::Local { .. } => true,
1295 ProjectClientState::Remote { .. } => false,
1296 }
1297 }
1298
1299 pub fn is_remote(&self) -> bool {
1300 !self.is_local()
1301 }
1302
1303 pub fn create_buffer(
1304 &mut self,
1305 text: &str,
1306 language: Option<Arc<Language>>,
1307 cx: &mut ModelContext<Self>,
1308 ) -> Result<ModelHandle<Buffer>> {
1309 if self.is_remote() {
1310 return Err(anyhow!("creating buffers as a guest is not supported yet"));
1311 }
1312
1313 let buffer = cx.add_model(|cx| {
1314 Buffer::new(self.replica_id(), text, cx)
1315 .with_language(language.unwrap_or(language::PLAIN_TEXT.clone()), cx)
1316 });
1317 self.register_buffer(&buffer, cx)?;
1318 Ok(buffer)
1319 }
1320
1321 pub fn open_path(
1322 &mut self,
1323 path: impl Into<ProjectPath>,
1324 cx: &mut ModelContext<Self>,
1325 ) -> Task<Result<(ProjectEntryId, AnyModelHandle)>> {
1326 let task = self.open_buffer(path, cx);
1327 cx.spawn_weak(|_, cx| async move {
1328 let buffer = task.await?;
1329 let project_entry_id = buffer
1330 .read_with(&cx, |buffer, cx| {
1331 File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
1332 })
1333 .ok_or_else(|| anyhow!("no project entry"))?;
1334 Ok((project_entry_id, buffer.into()))
1335 })
1336 }
1337
1338 pub fn open_local_buffer(
1339 &mut self,
1340 abs_path: impl AsRef<Path>,
1341 cx: &mut ModelContext<Self>,
1342 ) -> Task<Result<ModelHandle<Buffer>>> {
1343 if let Some((worktree, relative_path)) = self.find_local_worktree(abs_path.as_ref(), cx) {
1344 self.open_buffer((worktree.read(cx).id(), relative_path), cx)
1345 } else {
1346 Task::ready(Err(anyhow!("no such path")))
1347 }
1348 }
1349
1350 pub fn open_buffer(
1351 &mut self,
1352 path: impl Into<ProjectPath>,
1353 cx: &mut ModelContext<Self>,
1354 ) -> Task<Result<ModelHandle<Buffer>>> {
1355 let project_path = path.into();
1356 let worktree = if let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) {
1357 worktree
1358 } else {
1359 return Task::ready(Err(anyhow!("no such worktree")));
1360 };
1361
1362 // If there is already a buffer for the given path, then return it.
1363 let existing_buffer = self.get_open_buffer(&project_path, cx);
1364 if let Some(existing_buffer) = existing_buffer {
1365 return Task::ready(Ok(existing_buffer));
1366 }
1367
1368 let mut loading_watch = match self.loading_buffers.entry(project_path.clone()) {
1369 // If the given path is already being loaded, then wait for that existing
1370 // task to complete and return the same buffer.
1371 hash_map::Entry::Occupied(e) => e.get().clone(),
1372
1373 // Otherwise, record the fact that this path is now being loaded.
1374 hash_map::Entry::Vacant(entry) => {
1375 let (mut tx, rx) = postage::watch::channel();
1376 entry.insert(rx.clone());
1377
1378 let load_buffer = if worktree.read(cx).is_local() {
1379 self.open_local_buffer_internal(&project_path.path, &worktree, cx)
1380 } else {
1381 self.open_remote_buffer_internal(&project_path.path, &worktree, cx)
1382 };
1383
1384 cx.spawn(move |this, mut cx| async move {
1385 let load_result = load_buffer.await;
1386 *tx.borrow_mut() = Some(this.update(&mut cx, |this, _| {
1387 // Record the fact that the buffer is no longer loading.
1388 this.loading_buffers.remove(&project_path);
1389 let buffer = load_result.map_err(Arc::new)?;
1390 Ok(buffer)
1391 }));
1392 })
1393 .detach();
1394 rx
1395 }
1396 };
1397
1398 cx.foreground().spawn(async move {
1399 loop {
1400 if let Some(result) = loading_watch.borrow().as_ref() {
1401 match result {
1402 Ok(buffer) => return Ok(buffer.clone()),
1403 Err(error) => return Err(anyhow!("{}", error)),
1404 }
1405 }
1406 loading_watch.next().await;
1407 }
1408 })
1409 }
1410
1411 fn open_local_buffer_internal(
1412 &mut self,
1413 path: &Arc<Path>,
1414 worktree: &ModelHandle<Worktree>,
1415 cx: &mut ModelContext<Self>,
1416 ) -> Task<Result<ModelHandle<Buffer>>> {
1417 let load_buffer = worktree.update(cx, |worktree, cx| {
1418 let worktree = worktree.as_local_mut().unwrap();
1419 worktree.load_buffer(path, cx)
1420 });
1421 cx.spawn(|this, mut cx| async move {
1422 let buffer = load_buffer.await?;
1423 this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))?;
1424 Ok(buffer)
1425 })
1426 }
1427
1428 fn open_remote_buffer_internal(
1429 &mut self,
1430 path: &Arc<Path>,
1431 worktree: &ModelHandle<Worktree>,
1432 cx: &mut ModelContext<Self>,
1433 ) -> Task<Result<ModelHandle<Buffer>>> {
1434 let rpc = self.client.clone();
1435 let project_id = self.remote_id().unwrap();
1436 let remote_worktree_id = worktree.read(cx).id();
1437 let path = path.clone();
1438 let path_string = path.to_string_lossy().to_string();
1439 cx.spawn(|this, mut cx| async move {
1440 let response = rpc
1441 .request(proto::OpenBufferByPath {
1442 project_id,
1443 worktree_id: remote_worktree_id.to_proto(),
1444 path: path_string,
1445 })
1446 .await?;
1447 let buffer = response.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
1448 this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
1449 .await
1450 })
1451 }
1452
1453 fn open_local_buffer_via_lsp(
1454 &mut self,
1455 abs_path: lsp::Url,
1456 lsp_adapter: Arc<dyn LspAdapter>,
1457 lsp_server: Arc<LanguageServer>,
1458 cx: &mut ModelContext<Self>,
1459 ) -> Task<Result<ModelHandle<Buffer>>> {
1460 cx.spawn(|this, mut cx| async move {
1461 let abs_path = abs_path
1462 .to_file_path()
1463 .map_err(|_| anyhow!("can't convert URI to path"))?;
1464 let (worktree, relative_path) = if let Some(result) =
1465 this.read_with(&cx, |this, cx| this.find_local_worktree(&abs_path, cx))
1466 {
1467 result
1468 } else {
1469 let worktree = this
1470 .update(&mut cx, |this, cx| {
1471 this.create_local_worktree(&abs_path, false, cx)
1472 })
1473 .await?;
1474 this.update(&mut cx, |this, cx| {
1475 this.language_servers.insert(
1476 (worktree.read(cx).id(), lsp_adapter.name()),
1477 (lsp_adapter, lsp_server),
1478 );
1479 });
1480 (worktree, PathBuf::new())
1481 };
1482
1483 let project_path = ProjectPath {
1484 worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
1485 path: relative_path.into(),
1486 };
1487 this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
1488 .await
1489 })
1490 }
1491
1492 pub fn open_buffer_by_id(
1493 &mut self,
1494 id: u64,
1495 cx: &mut ModelContext<Self>,
1496 ) -> Task<Result<ModelHandle<Buffer>>> {
1497 if let Some(buffer) = self.buffer_for_id(id, cx) {
1498 Task::ready(Ok(buffer))
1499 } else if self.is_local() {
1500 Task::ready(Err(anyhow!("buffer {} does not exist", id)))
1501 } else if let Some(project_id) = self.remote_id() {
1502 let request = self
1503 .client
1504 .request(proto::OpenBufferById { project_id, id });
1505 cx.spawn(|this, mut cx| async move {
1506 let buffer = request
1507 .await?
1508 .buffer
1509 .ok_or_else(|| anyhow!("invalid buffer"))?;
1510 this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
1511 .await
1512 })
1513 } else {
1514 Task::ready(Err(anyhow!("cannot open buffer while disconnected")))
1515 }
1516 }
1517
1518 pub fn save_buffer_as(
1519 &mut self,
1520 buffer: ModelHandle<Buffer>,
1521 abs_path: PathBuf,
1522 cx: &mut ModelContext<Project>,
1523 ) -> Task<Result<()>> {
1524 let worktree_task = self.find_or_create_local_worktree(&abs_path, true, cx);
1525 let old_path =
1526 File::from_dyn(buffer.read(cx).file()).and_then(|f| Some(f.as_local()?.abs_path(cx)));
1527 cx.spawn(|this, mut cx| async move {
1528 if let Some(old_path) = old_path {
1529 this.update(&mut cx, |this, cx| {
1530 this.unregister_buffer_from_language_server(&buffer, old_path, cx);
1531 });
1532 }
1533 let (worktree, path) = worktree_task.await?;
1534 worktree
1535 .update(&mut cx, |worktree, cx| {
1536 worktree
1537 .as_local_mut()
1538 .unwrap()
1539 .save_buffer_as(buffer.clone(), path, cx)
1540 })
1541 .await?;
1542 this.update(&mut cx, |this, cx| {
1543 this.assign_language_to_buffer(&buffer, cx);
1544 this.register_buffer_with_language_server(&buffer, cx);
1545 });
1546 Ok(())
1547 })
1548 }
1549
1550 pub fn get_open_buffer(
1551 &mut self,
1552 path: &ProjectPath,
1553 cx: &mut ModelContext<Self>,
1554 ) -> Option<ModelHandle<Buffer>> {
1555 let worktree = self.worktree_for_id(path.worktree_id, cx)?;
1556 self.opened_buffers.values().find_map(|buffer| {
1557 let buffer = buffer.upgrade(cx)?;
1558 let file = File::from_dyn(buffer.read(cx).file())?;
1559 if file.worktree == worktree && file.path() == &path.path {
1560 Some(buffer)
1561 } else {
1562 None
1563 }
1564 })
1565 }
1566
1567 fn register_buffer(
1568 &mut self,
1569 buffer: &ModelHandle<Buffer>,
1570 cx: &mut ModelContext<Self>,
1571 ) -> Result<()> {
1572 let remote_id = buffer.read(cx).remote_id();
1573 let open_buffer = if self.is_remote() || self.is_shared() {
1574 OpenBuffer::Strong(buffer.clone())
1575 } else {
1576 OpenBuffer::Weak(buffer.downgrade())
1577 };
1578
1579 match self.opened_buffers.insert(remote_id, open_buffer) {
1580 None => {}
1581 Some(OpenBuffer::Loading(operations)) => {
1582 buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?
1583 }
1584 Some(OpenBuffer::Weak(existing_handle)) => {
1585 if existing_handle.upgrade(cx).is_some() {
1586 Err(anyhow!(
1587 "already registered buffer with remote id {}",
1588 remote_id
1589 ))?
1590 }
1591 }
1592 Some(OpenBuffer::Strong(_)) => Err(anyhow!(
1593 "already registered buffer with remote id {}",
1594 remote_id
1595 ))?,
1596 }
1597 cx.subscribe(buffer, |this, buffer, event, cx| {
1598 this.on_buffer_event(buffer, event, cx);
1599 })
1600 .detach();
1601
1602 self.assign_language_to_buffer(buffer, cx);
1603 self.register_buffer_with_language_server(buffer, cx);
1604 cx.observe_release(buffer, |this, buffer, cx| {
1605 if let Some(file) = File::from_dyn(buffer.file()) {
1606 if file.is_local() {
1607 let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
1608 if let Some((_, server)) = this.language_server_for_buffer(buffer, cx) {
1609 server
1610 .notify::<lsp::notification::DidCloseTextDocument>(
1611 lsp::DidCloseTextDocumentParams {
1612 text_document: lsp::TextDocumentIdentifier::new(uri.clone()),
1613 },
1614 )
1615 .log_err();
1616 }
1617 }
1618 }
1619 })
1620 .detach();
1621
1622 Ok(())
1623 }
1624
1625 fn register_buffer_with_language_server(
1626 &mut self,
1627 buffer_handle: &ModelHandle<Buffer>,
1628 cx: &mut ModelContext<Self>,
1629 ) {
1630 let buffer = buffer_handle.read(cx);
1631 let buffer_id = buffer.remote_id();
1632 if let Some(file) = File::from_dyn(buffer.file()) {
1633 if file.is_local() {
1634 let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
1635 let initial_snapshot = buffer.text_snapshot();
1636
1637 let mut language_server = None;
1638 let mut language_id = None;
1639 if let Some(language) = buffer.language() {
1640 let worktree_id = file.worktree_id(cx);
1641 if let Some(adapter) = language.lsp_adapter() {
1642 language_id = adapter.id_for_language(language.name().as_ref());
1643 language_server = self
1644 .language_servers
1645 .get(&(worktree_id, adapter.name()))
1646 .cloned();
1647 }
1648 }
1649
1650 if let Some(local_worktree) = file.worktree.read(cx).as_local() {
1651 if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) {
1652 self.update_buffer_diagnostics(&buffer_handle, diagnostics, None, cx)
1653 .log_err();
1654 }
1655 }
1656
1657 if let Some((_, server)) = language_server {
1658 server
1659 .notify::<lsp::notification::DidOpenTextDocument>(
1660 lsp::DidOpenTextDocumentParams {
1661 text_document: lsp::TextDocumentItem::new(
1662 uri,
1663 language_id.unwrap_or_default(),
1664 0,
1665 initial_snapshot.text(),
1666 ),
1667 }
1668 .clone(),
1669 )
1670 .log_err();
1671 buffer_handle.update(cx, |buffer, cx| {
1672 buffer.set_completion_triggers(
1673 server
1674 .capabilities()
1675 .completion_provider
1676 .as_ref()
1677 .and_then(|provider| provider.trigger_characters.clone())
1678 .unwrap_or(Vec::new()),
1679 cx,
1680 )
1681 });
1682 self.buffer_snapshots
1683 .insert(buffer_id, vec![(0, initial_snapshot)]);
1684 }
1685 }
1686 }
1687 }
1688
1689 fn unregister_buffer_from_language_server(
1690 &mut self,
1691 buffer: &ModelHandle<Buffer>,
1692 old_path: PathBuf,
1693 cx: &mut ModelContext<Self>,
1694 ) {
1695 buffer.update(cx, |buffer, cx| {
1696 buffer.update_diagnostics(Default::default(), cx);
1697 self.buffer_snapshots.remove(&buffer.remote_id());
1698 if let Some((_, language_server)) = self.language_server_for_buffer(buffer, cx) {
1699 language_server
1700 .notify::<lsp::notification::DidCloseTextDocument>(
1701 lsp::DidCloseTextDocumentParams {
1702 text_document: lsp::TextDocumentIdentifier::new(
1703 lsp::Url::from_file_path(old_path).unwrap(),
1704 ),
1705 },
1706 )
1707 .log_err();
1708 }
1709 });
1710 }
1711
1712 fn on_buffer_event(
1713 &mut self,
1714 buffer: ModelHandle<Buffer>,
1715 event: &BufferEvent,
1716 cx: &mut ModelContext<Self>,
1717 ) -> Option<()> {
1718 match event {
1719 BufferEvent::Operation(operation) => {
1720 if let Some(project_id) = self.shared_remote_id() {
1721 let request = self.client.request(proto::UpdateBuffer {
1722 project_id,
1723 buffer_id: buffer.read(cx).remote_id(),
1724 operations: vec![language::proto::serialize_operation(&operation)],
1725 });
1726 cx.background().spawn(request).detach_and_log_err(cx);
1727 }
1728 }
1729 BufferEvent::Edited { .. } => {
1730 let (_, language_server) = self
1731 .language_server_for_buffer(buffer.read(cx), cx)?
1732 .clone();
1733 let buffer = buffer.read(cx);
1734 let file = File::from_dyn(buffer.file())?;
1735 let abs_path = file.as_local()?.abs_path(cx);
1736 let uri = lsp::Url::from_file_path(abs_path).unwrap();
1737 let buffer_snapshots = self.buffer_snapshots.get_mut(&buffer.remote_id())?;
1738 let (version, prev_snapshot) = buffer_snapshots.last()?;
1739 let next_snapshot = buffer.text_snapshot();
1740 let next_version = version + 1;
1741
1742 let content_changes = buffer
1743 .edits_since::<(PointUtf16, usize)>(prev_snapshot.version())
1744 .map(|edit| {
1745 let edit_start = edit.new.start.0;
1746 let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
1747 let new_text = next_snapshot
1748 .text_for_range(edit.new.start.1..edit.new.end.1)
1749 .collect();
1750 lsp::TextDocumentContentChangeEvent {
1751 range: Some(lsp::Range::new(
1752 point_to_lsp(edit_start),
1753 point_to_lsp(edit_end),
1754 )),
1755 range_length: None,
1756 text: new_text,
1757 }
1758 })
1759 .collect();
1760
1761 buffer_snapshots.push((next_version, next_snapshot));
1762
1763 language_server
1764 .notify::<lsp::notification::DidChangeTextDocument>(
1765 lsp::DidChangeTextDocumentParams {
1766 text_document: lsp::VersionedTextDocumentIdentifier::new(
1767 uri,
1768 next_version,
1769 ),
1770 content_changes,
1771 },
1772 )
1773 .log_err();
1774 }
1775 BufferEvent::Saved => {
1776 let file = File::from_dyn(buffer.read(cx).file())?;
1777 let worktree_id = file.worktree_id(cx);
1778 let abs_path = file.as_local()?.abs_path(cx);
1779 let text_document = lsp::TextDocumentIdentifier {
1780 uri: lsp::Url::from_file_path(abs_path).unwrap(),
1781 };
1782
1783 for (_, server) in self.language_servers_for_worktree(worktree_id) {
1784 server
1785 .notify::<lsp::notification::DidSaveTextDocument>(
1786 lsp::DidSaveTextDocumentParams {
1787 text_document: text_document.clone(),
1788 text: None,
1789 },
1790 )
1791 .log_err();
1792 }
1793
1794 // After saving a buffer, simulate disk-based diagnostics being finished for languages
1795 // that don't support a disk-based progress token.
1796 let (lsp_adapter, language_server) =
1797 self.language_server_for_buffer(buffer.read(cx), cx)?;
1798 if lsp_adapter
1799 .disk_based_diagnostics_progress_token()
1800 .is_none()
1801 {
1802 let server_id = language_server.server_id();
1803 self.disk_based_diagnostics_finished(server_id, cx);
1804 self.broadcast_language_server_update(
1805 server_id,
1806 proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
1807 proto::LspDiskBasedDiagnosticsUpdated {},
1808 ),
1809 );
1810 }
1811 }
1812 _ => {}
1813 }
1814
1815 None
1816 }
1817
1818 fn language_servers_for_worktree(
1819 &self,
1820 worktree_id: WorktreeId,
1821 ) -> impl Iterator<Item = &(Arc<dyn LspAdapter>, Arc<LanguageServer>)> {
1822 self.language_servers.iter().filter_map(
1823 move |((language_server_worktree_id, _), server)| {
1824 if *language_server_worktree_id == worktree_id {
1825 Some(server)
1826 } else {
1827 None
1828 }
1829 },
1830 )
1831 }
1832
1833 fn assign_language_to_buffer(
1834 &mut self,
1835 buffer: &ModelHandle<Buffer>,
1836 cx: &mut ModelContext<Self>,
1837 ) -> Option<()> {
1838 // If the buffer has a language, set it and start the language server if we haven't already.
1839 let full_path = buffer.read(cx).file()?.full_path(cx);
1840 let language = self.languages.select_language(&full_path)?;
1841 buffer.update(cx, |buffer, cx| {
1842 buffer.set_language(Some(language.clone()), cx);
1843 });
1844
1845 let file = File::from_dyn(buffer.read(cx).file())?;
1846 let worktree = file.worktree.read(cx).as_local()?;
1847 let worktree_id = worktree.id();
1848 let worktree_abs_path = worktree.abs_path().clone();
1849 self.start_language_server(worktree_id, worktree_abs_path, language, cx);
1850
1851 None
1852 }
1853
1854 fn start_language_server(
1855 &mut self,
1856 worktree_id: WorktreeId,
1857 worktree_path: Arc<Path>,
1858 language: Arc<Language>,
1859 cx: &mut ModelContext<Self>,
1860 ) {
1861 let adapter = if let Some(adapter) = language.lsp_adapter() {
1862 adapter
1863 } else {
1864 return;
1865 };
1866 let key = (worktree_id, adapter.name());
1867 self.started_language_servers
1868 .entry(key.clone())
1869 .or_insert_with(|| {
1870 let server_id = post_inc(&mut self.next_language_server_id);
1871 let language_server = self.languages.start_language_server(
1872 server_id,
1873 language.clone(),
1874 worktree_path,
1875 self.client.http_client(),
1876 cx,
1877 );
1878 cx.spawn_weak(|this, mut cx| async move {
1879 let language_server = language_server?.await.log_err()?;
1880 let language_server = language_server
1881 .initialize(adapter.initialization_options())
1882 .await
1883 .log_err()?;
1884 let this = this.upgrade(&cx)?;
1885 let disk_based_diagnostics_progress_token =
1886 adapter.disk_based_diagnostics_progress_token();
1887
1888 language_server
1889 .on_notification::<lsp::notification::PublishDiagnostics, _>({
1890 let this = this.downgrade();
1891 let adapter = adapter.clone();
1892 move |params, mut cx| {
1893 if let Some(this) = this.upgrade(&cx) {
1894 this.update(&mut cx, |this, cx| {
1895 this.on_lsp_diagnostics_published(
1896 server_id, params, &adapter, cx,
1897 );
1898 });
1899 }
1900 }
1901 })
1902 .detach();
1903
1904 language_server
1905 .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
1906 let settings = this
1907 .read_with(&cx, |this, _| this.language_server_settings.clone());
1908 move |params, _| {
1909 let settings = settings.lock().clone();
1910 async move {
1911 Ok(params
1912 .items
1913 .into_iter()
1914 .map(|item| {
1915 if let Some(section) = &item.section {
1916 settings
1917 .get(section)
1918 .cloned()
1919 .unwrap_or(serde_json::Value::Null)
1920 } else {
1921 settings.clone()
1922 }
1923 })
1924 .collect())
1925 }
1926 }
1927 })
1928 .detach();
1929
1930 // Even though we don't have handling for these requests, respond to them to
1931 // avoid stalling any language server like `gopls` which waits for a response
1932 // to these requests when initializing.
1933 language_server
1934 .on_request::<lsp::request::WorkDoneProgressCreate, _, _>(|_, _| async {
1935 Ok(())
1936 })
1937 .detach();
1938 language_server
1939 .on_request::<lsp::request::RegisterCapability, _, _>(|_, _| async {
1940 Ok(())
1941 })
1942 .detach();
1943
1944 language_server
1945 .on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
1946 let this = this.downgrade();
1947 let adapter = adapter.clone();
1948 let language_server = language_server.clone();
1949 move |params, cx| {
1950 Self::on_lsp_workspace_edit(
1951 this,
1952 params,
1953 server_id,
1954 adapter.clone(),
1955 language_server.clone(),
1956 cx,
1957 )
1958 }
1959 })
1960 .detach();
1961
1962 language_server
1963 .on_notification::<lsp::notification::Progress, _>({
1964 let this = this.downgrade();
1965 move |params, mut cx| {
1966 if let Some(this) = this.upgrade(&cx) {
1967 this.update(&mut cx, |this, cx| {
1968 this.on_lsp_progress(
1969 params,
1970 server_id,
1971 disk_based_diagnostics_progress_token,
1972 cx,
1973 );
1974 });
1975 }
1976 }
1977 })
1978 .detach();
1979
1980 this.update(&mut cx, |this, cx| {
1981 this.language_servers
1982 .insert(key.clone(), (adapter.clone(), language_server.clone()));
1983 this.language_server_statuses.insert(
1984 server_id,
1985 LanguageServerStatus {
1986 name: language_server.name().to_string(),
1987 pending_work: Default::default(),
1988 pending_diagnostic_updates: 0,
1989 },
1990 );
1991 language_server
1992 .notify::<lsp::notification::DidChangeConfiguration>(
1993 lsp::DidChangeConfigurationParams {
1994 settings: this.language_server_settings.lock().clone(),
1995 },
1996 )
1997 .ok();
1998
1999 if let Some(project_id) = this.shared_remote_id() {
2000 this.client
2001 .send(proto::StartLanguageServer {
2002 project_id,
2003 server: Some(proto::LanguageServer {
2004 id: server_id as u64,
2005 name: language_server.name().to_string(),
2006 }),
2007 })
2008 .log_err();
2009 }
2010
2011 // Tell the language server about every open buffer in the worktree that matches the language.
2012 for buffer in this.opened_buffers.values() {
2013 if let Some(buffer_handle) = buffer.upgrade(cx) {
2014 let buffer = buffer_handle.read(cx);
2015 let file = if let Some(file) = File::from_dyn(buffer.file()) {
2016 file
2017 } else {
2018 continue;
2019 };
2020 let language = if let Some(language) = buffer.language() {
2021 language
2022 } else {
2023 continue;
2024 };
2025 if file.worktree.read(cx).id() != key.0
2026 || language.lsp_adapter().map(|a| a.name())
2027 != Some(key.1.clone())
2028 {
2029 continue;
2030 }
2031
2032 let file = file.as_local()?;
2033 let versions = this
2034 .buffer_snapshots
2035 .entry(buffer.remote_id())
2036 .or_insert_with(|| vec![(0, buffer.text_snapshot())]);
2037 let (version, initial_snapshot) = versions.last().unwrap();
2038 let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
2039 let language_id = adapter.id_for_language(language.name().as_ref());
2040 language_server
2041 .notify::<lsp::notification::DidOpenTextDocument>(
2042 lsp::DidOpenTextDocumentParams {
2043 text_document: lsp::TextDocumentItem::new(
2044 uri,
2045 language_id.unwrap_or_default(),
2046 *version,
2047 initial_snapshot.text(),
2048 ),
2049 },
2050 )
2051 .log_err()?;
2052 buffer_handle.update(cx, |buffer, cx| {
2053 buffer.set_completion_triggers(
2054 language_server
2055 .capabilities()
2056 .completion_provider
2057 .as_ref()
2058 .and_then(|provider| {
2059 provider.trigger_characters.clone()
2060 })
2061 .unwrap_or(Vec::new()),
2062 cx,
2063 )
2064 });
2065 }
2066 }
2067
2068 cx.notify();
2069 Some(())
2070 });
2071
2072 Some(language_server)
2073 })
2074 });
2075 }
2076
2077 pub fn restart_language_servers_for_buffers(
2078 &mut self,
2079 buffers: impl IntoIterator<Item = ModelHandle<Buffer>>,
2080 cx: &mut ModelContext<Self>,
2081 ) -> Option<()> {
2082 let language_server_lookup_info: HashSet<(WorktreeId, Arc<Path>, PathBuf)> = buffers
2083 .into_iter()
2084 .filter_map(|buffer| {
2085 let file = File::from_dyn(buffer.read(cx).file())?;
2086 let worktree = file.worktree.read(cx).as_local()?;
2087 let worktree_id = worktree.id();
2088 let worktree_abs_path = worktree.abs_path().clone();
2089 let full_path = file.full_path(cx);
2090 Some((worktree_id, worktree_abs_path, full_path))
2091 })
2092 .collect();
2093 for (worktree_id, worktree_abs_path, full_path) in language_server_lookup_info {
2094 let language = self.languages.select_language(&full_path)?;
2095 self.restart_language_server(worktree_id, worktree_abs_path, language, cx);
2096 }
2097
2098 None
2099 }
2100
2101 fn restart_language_server(
2102 &mut self,
2103 worktree_id: WorktreeId,
2104 worktree_path: Arc<Path>,
2105 language: Arc<Language>,
2106 cx: &mut ModelContext<Self>,
2107 ) {
2108 let adapter = if let Some(adapter) = language.lsp_adapter() {
2109 adapter
2110 } else {
2111 return;
2112 };
2113 let key = (worktree_id, adapter.name());
2114 let server_to_shutdown = self.language_servers.remove(&key);
2115 self.started_language_servers.remove(&key);
2116 server_to_shutdown
2117 .as_ref()
2118 .map(|(_, server)| self.language_server_statuses.remove(&server.server_id()));
2119 cx.spawn_weak(|this, mut cx| async move {
2120 if let Some(this) = this.upgrade(&cx) {
2121 if let Some((_, server_to_shutdown)) = server_to_shutdown {
2122 if let Some(shutdown_task) = server_to_shutdown.shutdown() {
2123 shutdown_task.await;
2124 }
2125 }
2126
2127 this.update(&mut cx, |this, cx| {
2128 this.start_language_server(worktree_id, worktree_path, language, cx);
2129 });
2130 }
2131 })
2132 .detach();
2133 }
2134
2135 fn on_lsp_diagnostics_published(
2136 &mut self,
2137 server_id: usize,
2138 mut params: lsp::PublishDiagnosticsParams,
2139 adapter: &Arc<dyn LspAdapter>,
2140 cx: &mut ModelContext<Self>,
2141 ) {
2142 adapter.process_diagnostics(&mut params);
2143 self.update_diagnostics(
2144 server_id,
2145 params,
2146 adapter.disk_based_diagnostic_sources(),
2147 cx,
2148 )
2149 .log_err();
2150 }
2151
2152 fn on_lsp_progress(
2153 &mut self,
2154 progress: lsp::ProgressParams,
2155 server_id: usize,
2156 disk_based_diagnostics_progress_token: Option<&str>,
2157 cx: &mut ModelContext<Self>,
2158 ) {
2159 let token = match progress.token {
2160 lsp::NumberOrString::String(token) => token,
2161 lsp::NumberOrString::Number(token) => {
2162 log::info!("skipping numeric progress token {}", token);
2163 return;
2164 }
2165 };
2166 let progress = match progress.value {
2167 lsp::ProgressParamsValue::WorkDone(value) => value,
2168 };
2169 let language_server_status =
2170 if let Some(status) = self.language_server_statuses.get_mut(&server_id) {
2171 status
2172 } else {
2173 return;
2174 };
2175 match progress {
2176 lsp::WorkDoneProgress::Begin(report) => {
2177 if Some(token.as_str()) == disk_based_diagnostics_progress_token {
2178 language_server_status.pending_diagnostic_updates += 1;
2179 if language_server_status.pending_diagnostic_updates == 1 {
2180 self.disk_based_diagnostics_started(server_id, cx);
2181 self.broadcast_language_server_update(
2182 server_id,
2183 proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
2184 proto::LspDiskBasedDiagnosticsUpdating {},
2185 ),
2186 );
2187 }
2188 } else {
2189 self.on_lsp_work_start(
2190 server_id,
2191 token.clone(),
2192 LanguageServerProgress {
2193 message: report.message.clone(),
2194 percentage: report.percentage.map(|p| p as usize),
2195 last_update_at: Instant::now(),
2196 },
2197 cx,
2198 );
2199 self.broadcast_language_server_update(
2200 server_id,
2201 proto::update_language_server::Variant::WorkStart(proto::LspWorkStart {
2202 token,
2203 message: report.message,
2204 percentage: report.percentage.map(|p| p as u32),
2205 }),
2206 );
2207 }
2208 }
2209 lsp::WorkDoneProgress::Report(report) => {
2210 if Some(token.as_str()) != disk_based_diagnostics_progress_token {
2211 self.on_lsp_work_progress(
2212 server_id,
2213 token.clone(),
2214 LanguageServerProgress {
2215 message: report.message.clone(),
2216 percentage: report.percentage.map(|p| p as usize),
2217 last_update_at: Instant::now(),
2218 },
2219 cx,
2220 );
2221 self.broadcast_language_server_update(
2222 server_id,
2223 proto::update_language_server::Variant::WorkProgress(
2224 proto::LspWorkProgress {
2225 token,
2226 message: report.message,
2227 percentage: report.percentage.map(|p| p as u32),
2228 },
2229 ),
2230 );
2231 }
2232 }
2233 lsp::WorkDoneProgress::End(_) => {
2234 if Some(token.as_str()) == disk_based_diagnostics_progress_token {
2235 language_server_status.pending_diagnostic_updates -= 1;
2236 if language_server_status.pending_diagnostic_updates == 0 {
2237 self.disk_based_diagnostics_finished(server_id, cx);
2238 self.broadcast_language_server_update(
2239 server_id,
2240 proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
2241 proto::LspDiskBasedDiagnosticsUpdated {},
2242 ),
2243 );
2244 }
2245 } else {
2246 self.on_lsp_work_end(server_id, token.clone(), cx);
2247 self.broadcast_language_server_update(
2248 server_id,
2249 proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd {
2250 token,
2251 }),
2252 );
2253 }
2254 }
2255 }
2256 }
2257
2258 fn on_lsp_work_start(
2259 &mut self,
2260 language_server_id: usize,
2261 token: String,
2262 progress: LanguageServerProgress,
2263 cx: &mut ModelContext<Self>,
2264 ) {
2265 if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
2266 status.pending_work.insert(token, progress);
2267 cx.notify();
2268 }
2269 }
2270
2271 fn on_lsp_work_progress(
2272 &mut self,
2273 language_server_id: usize,
2274 token: String,
2275 progress: LanguageServerProgress,
2276 cx: &mut ModelContext<Self>,
2277 ) {
2278 if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
2279 let entry = status
2280 .pending_work
2281 .entry(token)
2282 .or_insert(LanguageServerProgress {
2283 message: Default::default(),
2284 percentage: Default::default(),
2285 last_update_at: progress.last_update_at,
2286 });
2287 if progress.message.is_some() {
2288 entry.message = progress.message;
2289 }
2290 if progress.percentage.is_some() {
2291 entry.percentage = progress.percentage;
2292 }
2293 entry.last_update_at = progress.last_update_at;
2294 cx.notify();
2295 }
2296 }
2297
2298 fn on_lsp_work_end(
2299 &mut self,
2300 language_server_id: usize,
2301 token: String,
2302 cx: &mut ModelContext<Self>,
2303 ) {
2304 if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
2305 status.pending_work.remove(&token);
2306 cx.notify();
2307 }
2308 }
2309
2310 async fn on_lsp_workspace_edit(
2311 this: WeakModelHandle<Self>,
2312 params: lsp::ApplyWorkspaceEditParams,
2313 server_id: usize,
2314 adapter: Arc<dyn LspAdapter>,
2315 language_server: Arc<LanguageServer>,
2316 mut cx: AsyncAppContext,
2317 ) -> Result<lsp::ApplyWorkspaceEditResponse> {
2318 let this = this
2319 .upgrade(&cx)
2320 .ok_or_else(|| anyhow!("project project closed"))?;
2321 let transaction = Self::deserialize_workspace_edit(
2322 this.clone(),
2323 params.edit,
2324 true,
2325 adapter.clone(),
2326 language_server.clone(),
2327 &mut cx,
2328 )
2329 .await
2330 .log_err();
2331 this.update(&mut cx, |this, _| {
2332 if let Some(transaction) = transaction {
2333 this.last_workspace_edits_by_language_server
2334 .insert(server_id, transaction);
2335 }
2336 });
2337 Ok(lsp::ApplyWorkspaceEditResponse {
2338 applied: true,
2339 failed_change: None,
2340 failure_reason: None,
2341 })
2342 }
2343
2344 fn broadcast_language_server_update(
2345 &self,
2346 language_server_id: usize,
2347 event: proto::update_language_server::Variant,
2348 ) {
2349 if let Some(project_id) = self.shared_remote_id() {
2350 self.client
2351 .send(proto::UpdateLanguageServer {
2352 project_id,
2353 language_server_id: language_server_id as u64,
2354 variant: Some(event),
2355 })
2356 .log_err();
2357 }
2358 }
2359
2360 pub fn set_language_server_settings(&mut self, settings: serde_json::Value) {
2361 for (_, server) in self.language_servers.values() {
2362 server
2363 .notify::<lsp::notification::DidChangeConfiguration>(
2364 lsp::DidChangeConfigurationParams {
2365 settings: settings.clone(),
2366 },
2367 )
2368 .ok();
2369 }
2370 *self.language_server_settings.lock() = settings;
2371 }
2372
2373 pub fn language_server_statuses(
2374 &self,
2375 ) -> impl DoubleEndedIterator<Item = &LanguageServerStatus> {
2376 self.language_server_statuses.values()
2377 }
2378
2379 pub fn update_diagnostics(
2380 &mut self,
2381 language_server_id: usize,
2382 params: lsp::PublishDiagnosticsParams,
2383 disk_based_sources: &[&str],
2384 cx: &mut ModelContext<Self>,
2385 ) -> Result<()> {
2386 let abs_path = params
2387 .uri
2388 .to_file_path()
2389 .map_err(|_| anyhow!("URI is not a file"))?;
2390 let mut diagnostics = Vec::default();
2391 let mut primary_diagnostic_group_ids = HashMap::default();
2392 let mut sources_by_group_id = HashMap::default();
2393 let mut supporting_diagnostics = HashMap::default();
2394 for diagnostic in ¶ms.diagnostics {
2395 let source = diagnostic.source.as_ref();
2396 let code = diagnostic.code.as_ref().map(|code| match code {
2397 lsp::NumberOrString::Number(code) => code.to_string(),
2398 lsp::NumberOrString::String(code) => code.clone(),
2399 });
2400 let range = range_from_lsp(diagnostic.range);
2401 let is_supporting = diagnostic
2402 .related_information
2403 .as_ref()
2404 .map_or(false, |infos| {
2405 infos.iter().any(|info| {
2406 primary_diagnostic_group_ids.contains_key(&(
2407 source,
2408 code.clone(),
2409 range_from_lsp(info.location.range),
2410 ))
2411 })
2412 });
2413
2414 let is_unnecessary = diagnostic.tags.as_ref().map_or(false, |tags| {
2415 tags.iter().any(|tag| *tag == DiagnosticTag::UNNECESSARY)
2416 });
2417
2418 if is_supporting {
2419 supporting_diagnostics.insert(
2420 (source, code.clone(), range),
2421 (diagnostic.severity, is_unnecessary),
2422 );
2423 } else {
2424 let group_id = post_inc(&mut self.next_diagnostic_group_id);
2425 let is_disk_based = source.map_or(false, |source| {
2426 disk_based_sources.contains(&source.as_str())
2427 });
2428
2429 sources_by_group_id.insert(group_id, source);
2430 primary_diagnostic_group_ids
2431 .insert((source, code.clone(), range.clone()), group_id);
2432
2433 diagnostics.push(DiagnosticEntry {
2434 range,
2435 diagnostic: Diagnostic {
2436 code: code.clone(),
2437 severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
2438 message: diagnostic.message.clone(),
2439 group_id,
2440 is_primary: true,
2441 is_valid: true,
2442 is_disk_based,
2443 is_unnecessary,
2444 },
2445 });
2446 if let Some(infos) = &diagnostic.related_information {
2447 for info in infos {
2448 if info.location.uri == params.uri && !info.message.is_empty() {
2449 let range = range_from_lsp(info.location.range);
2450 diagnostics.push(DiagnosticEntry {
2451 range,
2452 diagnostic: Diagnostic {
2453 code: code.clone(),
2454 severity: DiagnosticSeverity::INFORMATION,
2455 message: info.message.clone(),
2456 group_id,
2457 is_primary: false,
2458 is_valid: true,
2459 is_disk_based,
2460 is_unnecessary: false,
2461 },
2462 });
2463 }
2464 }
2465 }
2466 }
2467 }
2468
2469 for entry in &mut diagnostics {
2470 let diagnostic = &mut entry.diagnostic;
2471 if !diagnostic.is_primary {
2472 let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap();
2473 if let Some(&(severity, is_unnecessary)) = supporting_diagnostics.get(&(
2474 source,
2475 diagnostic.code.clone(),
2476 entry.range.clone(),
2477 )) {
2478 if let Some(severity) = severity {
2479 diagnostic.severity = severity;
2480 }
2481 diagnostic.is_unnecessary = is_unnecessary;
2482 }
2483 }
2484 }
2485
2486 self.update_diagnostic_entries(
2487 language_server_id,
2488 abs_path,
2489 params.version,
2490 diagnostics,
2491 cx,
2492 )?;
2493 Ok(())
2494 }
2495
2496 pub fn update_diagnostic_entries(
2497 &mut self,
2498 language_server_id: usize,
2499 abs_path: PathBuf,
2500 version: Option<i32>,
2501 diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
2502 cx: &mut ModelContext<Project>,
2503 ) -> Result<(), anyhow::Error> {
2504 let (worktree, relative_path) = self
2505 .find_local_worktree(&abs_path, cx)
2506 .ok_or_else(|| anyhow!("no worktree found for diagnostics"))?;
2507 if !worktree.read(cx).is_visible() {
2508 return Ok(());
2509 }
2510
2511 let project_path = ProjectPath {
2512 worktree_id: worktree.read(cx).id(),
2513 path: relative_path.into(),
2514 };
2515 if let Some(buffer) = self.get_open_buffer(&project_path, cx) {
2516 self.update_buffer_diagnostics(&buffer, diagnostics.clone(), version, cx)?;
2517 }
2518
2519 let updated = worktree.update(cx, |worktree, cx| {
2520 worktree
2521 .as_local_mut()
2522 .ok_or_else(|| anyhow!("not a local worktree"))?
2523 .update_diagnostics(
2524 language_server_id,
2525 project_path.path.clone(),
2526 diagnostics,
2527 cx,
2528 )
2529 })?;
2530 if updated {
2531 cx.emit(Event::DiagnosticsUpdated {
2532 language_server_id,
2533 path: project_path,
2534 });
2535 }
2536 Ok(())
2537 }
2538
2539 fn update_buffer_diagnostics(
2540 &mut self,
2541 buffer: &ModelHandle<Buffer>,
2542 mut diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
2543 version: Option<i32>,
2544 cx: &mut ModelContext<Self>,
2545 ) -> Result<()> {
2546 fn compare_diagnostics(a: &Diagnostic, b: &Diagnostic) -> Ordering {
2547 Ordering::Equal
2548 .then_with(|| b.is_primary.cmp(&a.is_primary))
2549 .then_with(|| a.is_disk_based.cmp(&b.is_disk_based))
2550 .then_with(|| a.severity.cmp(&b.severity))
2551 .then_with(|| a.message.cmp(&b.message))
2552 }
2553
2554 let snapshot = self.buffer_snapshot_for_lsp_version(buffer, version, cx)?;
2555
2556 diagnostics.sort_unstable_by(|a, b| {
2557 Ordering::Equal
2558 .then_with(|| a.range.start.cmp(&b.range.start))
2559 .then_with(|| b.range.end.cmp(&a.range.end))
2560 .then_with(|| compare_diagnostics(&a.diagnostic, &b.diagnostic))
2561 });
2562
2563 let mut sanitized_diagnostics = Vec::new();
2564 let edits_since_save = Patch::new(
2565 snapshot
2566 .edits_since::<PointUtf16>(buffer.read(cx).saved_version())
2567 .collect(),
2568 );
2569 for entry in diagnostics {
2570 let start;
2571 let end;
2572 if entry.diagnostic.is_disk_based {
2573 // Some diagnostics are based on files on disk instead of buffers'
2574 // current contents. Adjust these diagnostics' ranges to reflect
2575 // any unsaved edits.
2576 start = edits_since_save.old_to_new(entry.range.start);
2577 end = edits_since_save.old_to_new(entry.range.end);
2578 } else {
2579 start = entry.range.start;
2580 end = entry.range.end;
2581 }
2582
2583 let mut range = snapshot.clip_point_utf16(start, Bias::Left)
2584 ..snapshot.clip_point_utf16(end, Bias::Right);
2585
2586 // Expand empty ranges by one character
2587 if range.start == range.end {
2588 range.end.column += 1;
2589 range.end = snapshot.clip_point_utf16(range.end, Bias::Right);
2590 if range.start == range.end && range.end.column > 0 {
2591 range.start.column -= 1;
2592 range.start = snapshot.clip_point_utf16(range.start, Bias::Left);
2593 }
2594 }
2595
2596 sanitized_diagnostics.push(DiagnosticEntry {
2597 range,
2598 diagnostic: entry.diagnostic,
2599 });
2600 }
2601 drop(edits_since_save);
2602
2603 let set = DiagnosticSet::new(sanitized_diagnostics, &snapshot);
2604 buffer.update(cx, |buffer, cx| buffer.update_diagnostics(set, cx));
2605 Ok(())
2606 }
2607
2608 pub fn reload_buffers(
2609 &self,
2610 buffers: HashSet<ModelHandle<Buffer>>,
2611 push_to_history: bool,
2612 cx: &mut ModelContext<Self>,
2613 ) -> Task<Result<ProjectTransaction>> {
2614 let mut local_buffers = Vec::new();
2615 let mut remote_buffers = None;
2616 for buffer_handle in buffers {
2617 let buffer = buffer_handle.read(cx);
2618 if buffer.is_dirty() {
2619 if let Some(file) = File::from_dyn(buffer.file()) {
2620 if file.is_local() {
2621 local_buffers.push(buffer_handle);
2622 } else {
2623 remote_buffers.get_or_insert(Vec::new()).push(buffer_handle);
2624 }
2625 }
2626 }
2627 }
2628
2629 let remote_buffers = self.remote_id().zip(remote_buffers);
2630 let client = self.client.clone();
2631
2632 cx.spawn(|this, mut cx| async move {
2633 let mut project_transaction = ProjectTransaction::default();
2634
2635 if let Some((project_id, remote_buffers)) = remote_buffers {
2636 let response = client
2637 .request(proto::ReloadBuffers {
2638 project_id,
2639 buffer_ids: remote_buffers
2640 .iter()
2641 .map(|buffer| buffer.read_with(&cx, |buffer, _| buffer.remote_id()))
2642 .collect(),
2643 })
2644 .await?
2645 .transaction
2646 .ok_or_else(|| anyhow!("missing transaction"))?;
2647 project_transaction = this
2648 .update(&mut cx, |this, cx| {
2649 this.deserialize_project_transaction(response, push_to_history, cx)
2650 })
2651 .await?;
2652 }
2653
2654 for buffer in local_buffers {
2655 let transaction = buffer
2656 .update(&mut cx, |buffer, cx| buffer.reload(cx))
2657 .await?;
2658 buffer.update(&mut cx, |buffer, cx| {
2659 if let Some(transaction) = transaction {
2660 if !push_to_history {
2661 buffer.forget_transaction(transaction.id);
2662 }
2663 project_transaction.0.insert(cx.handle(), transaction);
2664 }
2665 });
2666 }
2667
2668 Ok(project_transaction)
2669 })
2670 }
2671
2672 pub fn format(
2673 &self,
2674 buffers: HashSet<ModelHandle<Buffer>>,
2675 push_to_history: bool,
2676 cx: &mut ModelContext<Project>,
2677 ) -> Task<Result<ProjectTransaction>> {
2678 let mut local_buffers = Vec::new();
2679 let mut remote_buffers = None;
2680 for buffer_handle in buffers {
2681 let buffer = buffer_handle.read(cx);
2682 if let Some(file) = File::from_dyn(buffer.file()) {
2683 if let Some(buffer_abs_path) = file.as_local().map(|f| f.abs_path(cx)) {
2684 if let Some((_, server)) = self.language_server_for_buffer(buffer, cx) {
2685 local_buffers.push((buffer_handle, buffer_abs_path, server.clone()));
2686 }
2687 } else {
2688 remote_buffers.get_or_insert(Vec::new()).push(buffer_handle);
2689 }
2690 } else {
2691 return Task::ready(Ok(Default::default()));
2692 }
2693 }
2694
2695 let remote_buffers = self.remote_id().zip(remote_buffers);
2696 let client = self.client.clone();
2697
2698 cx.spawn(|this, mut cx| async move {
2699 let mut project_transaction = ProjectTransaction::default();
2700
2701 if let Some((project_id, remote_buffers)) = remote_buffers {
2702 let response = client
2703 .request(proto::FormatBuffers {
2704 project_id,
2705 buffer_ids: remote_buffers
2706 .iter()
2707 .map(|buffer| buffer.read_with(&cx, |buffer, _| buffer.remote_id()))
2708 .collect(),
2709 })
2710 .await?
2711 .transaction
2712 .ok_or_else(|| anyhow!("missing transaction"))?;
2713 project_transaction = this
2714 .update(&mut cx, |this, cx| {
2715 this.deserialize_project_transaction(response, push_to_history, cx)
2716 })
2717 .await?;
2718 }
2719
2720 for (buffer, buffer_abs_path, language_server) in local_buffers {
2721 let text_document = lsp::TextDocumentIdentifier::new(
2722 lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
2723 );
2724 let capabilities = &language_server.capabilities();
2725 let tab_size = cx.update(|cx| {
2726 let language_name = buffer.read(cx).language().map(|language| language.name());
2727 cx.global::<Settings>().tab_size(language_name.as_deref())
2728 });
2729 let lsp_edits = if capabilities
2730 .document_formatting_provider
2731 .as_ref()
2732 .map_or(false, |provider| *provider != lsp::OneOf::Left(false))
2733 {
2734 language_server
2735 .request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
2736 text_document,
2737 options: lsp::FormattingOptions {
2738 tab_size,
2739 insert_spaces: true,
2740 insert_final_newline: Some(true),
2741 ..Default::default()
2742 },
2743 work_done_progress_params: Default::default(),
2744 })
2745 .await?
2746 } else if capabilities
2747 .document_range_formatting_provider
2748 .as_ref()
2749 .map_or(false, |provider| *provider != lsp::OneOf::Left(false))
2750 {
2751 let buffer_start = lsp::Position::new(0, 0);
2752 let buffer_end =
2753 buffer.read_with(&cx, |buffer, _| point_to_lsp(buffer.max_point_utf16()));
2754 language_server
2755 .request::<lsp::request::RangeFormatting>(
2756 lsp::DocumentRangeFormattingParams {
2757 text_document,
2758 range: lsp::Range::new(buffer_start, buffer_end),
2759 options: lsp::FormattingOptions {
2760 tab_size: 4,
2761 insert_spaces: true,
2762 insert_final_newline: Some(true),
2763 ..Default::default()
2764 },
2765 work_done_progress_params: Default::default(),
2766 },
2767 )
2768 .await?
2769 } else {
2770 continue;
2771 };
2772
2773 if let Some(lsp_edits) = lsp_edits {
2774 let edits = this
2775 .update(&mut cx, |this, cx| {
2776 this.edits_from_lsp(&buffer, lsp_edits, None, cx)
2777 })
2778 .await?;
2779 buffer.update(&mut cx, |buffer, cx| {
2780 buffer.finalize_last_transaction();
2781 buffer.start_transaction();
2782 for (range, text) in edits {
2783 buffer.edit([(range, text)], cx);
2784 }
2785 if buffer.end_transaction(cx).is_some() {
2786 let transaction = buffer.finalize_last_transaction().unwrap().clone();
2787 if !push_to_history {
2788 buffer.forget_transaction(transaction.id);
2789 }
2790 project_transaction.0.insert(cx.handle(), transaction);
2791 }
2792 });
2793 }
2794 }
2795
2796 Ok(project_transaction)
2797 })
2798 }
2799
2800 pub fn definition<T: ToPointUtf16>(
2801 &self,
2802 buffer: &ModelHandle<Buffer>,
2803 position: T,
2804 cx: &mut ModelContext<Self>,
2805 ) -> Task<Result<Vec<Location>>> {
2806 let position = position.to_point_utf16(buffer.read(cx));
2807 self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
2808 }
2809
2810 pub fn references<T: ToPointUtf16>(
2811 &self,
2812 buffer: &ModelHandle<Buffer>,
2813 position: T,
2814 cx: &mut ModelContext<Self>,
2815 ) -> Task<Result<Vec<Location>>> {
2816 let position = position.to_point_utf16(buffer.read(cx));
2817 self.request_lsp(buffer.clone(), GetReferences { position }, cx)
2818 }
2819
2820 pub fn document_highlights<T: ToPointUtf16>(
2821 &self,
2822 buffer: &ModelHandle<Buffer>,
2823 position: T,
2824 cx: &mut ModelContext<Self>,
2825 ) -> Task<Result<Vec<DocumentHighlight>>> {
2826 let position = position.to_point_utf16(buffer.read(cx));
2827
2828 self.request_lsp(buffer.clone(), GetDocumentHighlights { position }, cx)
2829 }
2830
2831 pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
2832 if self.is_local() {
2833 let mut requests = Vec::new();
2834 for ((worktree_id, _), (lsp_adapter, language_server)) in self.language_servers.iter() {
2835 let worktree_id = *worktree_id;
2836 if let Some(worktree) = self
2837 .worktree_for_id(worktree_id, cx)
2838 .and_then(|worktree| worktree.read(cx).as_local())
2839 {
2840 let lsp_adapter = lsp_adapter.clone();
2841 let worktree_abs_path = worktree.abs_path().clone();
2842 requests.push(
2843 language_server
2844 .request::<lsp::request::WorkspaceSymbol>(lsp::WorkspaceSymbolParams {
2845 query: query.to_string(),
2846 ..Default::default()
2847 })
2848 .log_err()
2849 .map(move |response| {
2850 (
2851 lsp_adapter,
2852 worktree_id,
2853 worktree_abs_path,
2854 response.unwrap_or_default(),
2855 )
2856 }),
2857 );
2858 }
2859 }
2860
2861 cx.spawn_weak(|this, cx| async move {
2862 let responses = futures::future::join_all(requests).await;
2863 let this = if let Some(this) = this.upgrade(&cx) {
2864 this
2865 } else {
2866 return Ok(Default::default());
2867 };
2868 this.read_with(&cx, |this, cx| {
2869 let mut symbols = Vec::new();
2870 for (adapter, source_worktree_id, worktree_abs_path, response) in responses {
2871 symbols.extend(response.into_iter().flatten().filter_map(|lsp_symbol| {
2872 let abs_path = lsp_symbol.location.uri.to_file_path().ok()?;
2873 let mut worktree_id = source_worktree_id;
2874 let path;
2875 if let Some((worktree, rel_path)) =
2876 this.find_local_worktree(&abs_path, cx)
2877 {
2878 worktree_id = worktree.read(cx).id();
2879 path = rel_path;
2880 } else {
2881 path = relativize_path(&worktree_abs_path, &abs_path);
2882 }
2883
2884 let label = this
2885 .languages
2886 .select_language(&path)
2887 .and_then(|language| {
2888 language.label_for_symbol(&lsp_symbol.name, lsp_symbol.kind)
2889 })
2890 .unwrap_or_else(|| CodeLabel::plain(lsp_symbol.name.clone(), None));
2891 let signature = this.symbol_signature(worktree_id, &path);
2892
2893 Some(Symbol {
2894 source_worktree_id,
2895 worktree_id,
2896 language_server_name: adapter.name(),
2897 name: lsp_symbol.name,
2898 kind: lsp_symbol.kind,
2899 label,
2900 path,
2901 range: range_from_lsp(lsp_symbol.location.range),
2902 signature,
2903 })
2904 }));
2905 }
2906 Ok(symbols)
2907 })
2908 })
2909 } else if let Some(project_id) = self.remote_id() {
2910 let request = self.client.request(proto::GetProjectSymbols {
2911 project_id,
2912 query: query.to_string(),
2913 });
2914 cx.spawn_weak(|this, cx| async move {
2915 let response = request.await?;
2916 let mut symbols = Vec::new();
2917 if let Some(this) = this.upgrade(&cx) {
2918 this.read_with(&cx, |this, _| {
2919 symbols.extend(
2920 response
2921 .symbols
2922 .into_iter()
2923 .filter_map(|symbol| this.deserialize_symbol(symbol).log_err()),
2924 );
2925 })
2926 }
2927 Ok(symbols)
2928 })
2929 } else {
2930 Task::ready(Ok(Default::default()))
2931 }
2932 }
2933
2934 pub fn open_buffer_for_symbol(
2935 &mut self,
2936 symbol: &Symbol,
2937 cx: &mut ModelContext<Self>,
2938 ) -> Task<Result<ModelHandle<Buffer>>> {
2939 if self.is_local() {
2940 let (lsp_adapter, language_server) = if let Some(server) = self.language_servers.get(&(
2941 symbol.source_worktree_id,
2942 symbol.language_server_name.clone(),
2943 )) {
2944 server.clone()
2945 } else {
2946 return Task::ready(Err(anyhow!(
2947 "language server for worktree and language not found"
2948 )));
2949 };
2950
2951 let worktree_abs_path = if let Some(worktree_abs_path) = self
2952 .worktree_for_id(symbol.worktree_id, cx)
2953 .and_then(|worktree| worktree.read(cx).as_local())
2954 .map(|local_worktree| local_worktree.abs_path())
2955 {
2956 worktree_abs_path
2957 } else {
2958 return Task::ready(Err(anyhow!("worktree not found for symbol")));
2959 };
2960 let symbol_abs_path = worktree_abs_path.join(&symbol.path);
2961 let symbol_uri = if let Ok(uri) = lsp::Url::from_file_path(symbol_abs_path) {
2962 uri
2963 } else {
2964 return Task::ready(Err(anyhow!("invalid symbol path")));
2965 };
2966
2967 self.open_local_buffer_via_lsp(symbol_uri, lsp_adapter, language_server, cx)
2968 } else if let Some(project_id) = self.remote_id() {
2969 let request = self.client.request(proto::OpenBufferForSymbol {
2970 project_id,
2971 symbol: Some(serialize_symbol(symbol)),
2972 });
2973 cx.spawn(|this, mut cx| async move {
2974 let response = request.await?;
2975 let buffer = response.buffer.ok_or_else(|| anyhow!("invalid buffer"))?;
2976 this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
2977 .await
2978 })
2979 } else {
2980 Task::ready(Err(anyhow!("project does not have a remote id")))
2981 }
2982 }
2983
2984 pub fn hover<T: ToPointUtf16>(
2985 &self,
2986 buffer: &ModelHandle<Buffer>,
2987 position: T,
2988 cx: &mut ModelContext<Self>,
2989 ) -> Task<Result<Option<Hover>>> {
2990 let position = position.to_point_utf16(buffer.read(cx));
2991 self.request_lsp(buffer.clone(), GetHover { position }, cx)
2992 }
2993
2994 pub fn completions<T: ToPointUtf16>(
2995 &self,
2996 source_buffer_handle: &ModelHandle<Buffer>,
2997 position: T,
2998 cx: &mut ModelContext<Self>,
2999 ) -> Task<Result<Vec<Completion>>> {
3000 let source_buffer_handle = source_buffer_handle.clone();
3001 let source_buffer = source_buffer_handle.read(cx);
3002 let buffer_id = source_buffer.remote_id();
3003 let language = source_buffer.language().cloned();
3004 let worktree;
3005 let buffer_abs_path;
3006 if let Some(file) = File::from_dyn(source_buffer.file()) {
3007 worktree = file.worktree.clone();
3008 buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
3009 } else {
3010 return Task::ready(Ok(Default::default()));
3011 };
3012
3013 let position = position.to_point_utf16(source_buffer);
3014 let anchor = source_buffer.anchor_after(position);
3015
3016 if worktree.read(cx).as_local().is_some() {
3017 let buffer_abs_path = buffer_abs_path.unwrap();
3018 let (_, lang_server) =
3019 if let Some(server) = self.language_server_for_buffer(source_buffer, cx) {
3020 server.clone()
3021 } else {
3022 return Task::ready(Ok(Default::default()));
3023 };
3024
3025 cx.spawn(|_, cx| async move {
3026 let completions = lang_server
3027 .request::<lsp::request::Completion>(lsp::CompletionParams {
3028 text_document_position: lsp::TextDocumentPositionParams::new(
3029 lsp::TextDocumentIdentifier::new(
3030 lsp::Url::from_file_path(buffer_abs_path).unwrap(),
3031 ),
3032 point_to_lsp(position),
3033 ),
3034 context: Default::default(),
3035 work_done_progress_params: Default::default(),
3036 partial_result_params: Default::default(),
3037 })
3038 .await
3039 .context("lsp completion request failed")?;
3040
3041 let completions = if let Some(completions) = completions {
3042 match completions {
3043 lsp::CompletionResponse::Array(completions) => completions,
3044 lsp::CompletionResponse::List(list) => list.items,
3045 }
3046 } else {
3047 Default::default()
3048 };
3049
3050 source_buffer_handle.read_with(&cx, |this, _| {
3051 let snapshot = this.snapshot();
3052 let clipped_position = this.clip_point_utf16(position, Bias::Left);
3053 let mut range_for_token = None;
3054 Ok(completions
3055 .into_iter()
3056 .filter_map(|lsp_completion| {
3057 // For now, we can only handle additional edits if they are returned
3058 // when resolving the completion, not if they are present initially.
3059 if lsp_completion
3060 .additional_text_edits
3061 .as_ref()
3062 .map_or(false, |edits| !edits.is_empty())
3063 {
3064 return None;
3065 }
3066
3067 let (old_range, new_text) = match lsp_completion.text_edit.as_ref() {
3068 // If the language server provides a range to overwrite, then
3069 // check that the range is valid.
3070 Some(lsp::CompletionTextEdit::Edit(edit)) => {
3071 let range = range_from_lsp(edit.range);
3072 let start = snapshot.clip_point_utf16(range.start, Bias::Left);
3073 let end = snapshot.clip_point_utf16(range.end, Bias::Left);
3074 if start != range.start || end != range.end {
3075 log::info!("completion out of expected range");
3076 return None;
3077 }
3078 (
3079 snapshot.anchor_before(start)..snapshot.anchor_after(end),
3080 edit.new_text.clone(),
3081 )
3082 }
3083 // If the language server does not provide a range, then infer
3084 // the range based on the syntax tree.
3085 None => {
3086 if position != clipped_position {
3087 log::info!("completion out of expected range");
3088 return None;
3089 }
3090 let Range { start, end } = range_for_token
3091 .get_or_insert_with(|| {
3092 let offset = position.to_offset(&snapshot);
3093 snapshot
3094 .range_for_word_token_at(offset)
3095 .unwrap_or_else(|| offset..offset)
3096 })
3097 .clone();
3098 let text = lsp_completion
3099 .insert_text
3100 .as_ref()
3101 .unwrap_or(&lsp_completion.label)
3102 .clone();
3103 (
3104 snapshot.anchor_before(start)..snapshot.anchor_after(end),
3105 text.clone(),
3106 )
3107 }
3108 Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {
3109 log::info!("unsupported insert/replace completion");
3110 return None;
3111 }
3112 };
3113
3114 Some(Completion {
3115 old_range,
3116 new_text,
3117 label: language
3118 .as_ref()
3119 .and_then(|l| l.label_for_completion(&lsp_completion))
3120 .unwrap_or_else(|| {
3121 CodeLabel::plain(
3122 lsp_completion.label.clone(),
3123 lsp_completion.filter_text.as_deref(),
3124 )
3125 }),
3126 lsp_completion,
3127 })
3128 })
3129 .collect())
3130 })
3131 })
3132 } else if let Some(project_id) = self.remote_id() {
3133 let rpc = self.client.clone();
3134 let message = proto::GetCompletions {
3135 project_id,
3136 buffer_id,
3137 position: Some(language::proto::serialize_anchor(&anchor)),
3138 version: serialize_version(&source_buffer.version()),
3139 };
3140 cx.spawn_weak(|_, mut cx| async move {
3141 let response = rpc.request(message).await?;
3142
3143 source_buffer_handle
3144 .update(&mut cx, |buffer, _| {
3145 buffer.wait_for_version(deserialize_version(response.version))
3146 })
3147 .await;
3148
3149 response
3150 .completions
3151 .into_iter()
3152 .map(|completion| {
3153 language::proto::deserialize_completion(completion, language.as_ref())
3154 })
3155 .collect()
3156 })
3157 } else {
3158 Task::ready(Ok(Default::default()))
3159 }
3160 }
3161
3162 pub fn apply_additional_edits_for_completion(
3163 &self,
3164 buffer_handle: ModelHandle<Buffer>,
3165 completion: Completion,
3166 push_to_history: bool,
3167 cx: &mut ModelContext<Self>,
3168 ) -> Task<Result<Option<Transaction>>> {
3169 let buffer = buffer_handle.read(cx);
3170 let buffer_id = buffer.remote_id();
3171
3172 if self.is_local() {
3173 let (_, lang_server) = if let Some(server) = self.language_server_for_buffer(buffer, cx)
3174 {
3175 server.clone()
3176 } else {
3177 return Task::ready(Ok(Default::default()));
3178 };
3179
3180 cx.spawn(|this, mut cx| async move {
3181 let resolved_completion = lang_server
3182 .request::<lsp::request::ResolveCompletionItem>(completion.lsp_completion)
3183 .await?;
3184 if let Some(edits) = resolved_completion.additional_text_edits {
3185 let edits = this
3186 .update(&mut cx, |this, cx| {
3187 this.edits_from_lsp(&buffer_handle, edits, None, cx)
3188 })
3189 .await?;
3190 buffer_handle.update(&mut cx, |buffer, cx| {
3191 buffer.finalize_last_transaction();
3192 buffer.start_transaction();
3193 for (range, text) in edits {
3194 buffer.edit([(range, text)], cx);
3195 }
3196 let transaction = if buffer.end_transaction(cx).is_some() {
3197 let transaction = buffer.finalize_last_transaction().unwrap().clone();
3198 if !push_to_history {
3199 buffer.forget_transaction(transaction.id);
3200 }
3201 Some(transaction)
3202 } else {
3203 None
3204 };
3205 Ok(transaction)
3206 })
3207 } else {
3208 Ok(None)
3209 }
3210 })
3211 } else if let Some(project_id) = self.remote_id() {
3212 let client = self.client.clone();
3213 cx.spawn(|_, mut cx| async move {
3214 let response = client
3215 .request(proto::ApplyCompletionAdditionalEdits {
3216 project_id,
3217 buffer_id,
3218 completion: Some(language::proto::serialize_completion(&completion)),
3219 })
3220 .await?;
3221
3222 if let Some(transaction) = response.transaction {
3223 let transaction = language::proto::deserialize_transaction(transaction)?;
3224 buffer_handle
3225 .update(&mut cx, |buffer, _| {
3226 buffer.wait_for_edits(transaction.edit_ids.iter().copied())
3227 })
3228 .await;
3229 if push_to_history {
3230 buffer_handle.update(&mut cx, |buffer, _| {
3231 buffer.push_transaction(transaction.clone(), Instant::now());
3232 });
3233 }
3234 Ok(Some(transaction))
3235 } else {
3236 Ok(None)
3237 }
3238 })
3239 } else {
3240 Task::ready(Err(anyhow!("project does not have a remote id")))
3241 }
3242 }
3243
3244 pub fn code_actions<T: Clone + ToOffset>(
3245 &self,
3246 buffer_handle: &ModelHandle<Buffer>,
3247 range: Range<T>,
3248 cx: &mut ModelContext<Self>,
3249 ) -> Task<Result<Vec<CodeAction>>> {
3250 let buffer_handle = buffer_handle.clone();
3251 let buffer = buffer_handle.read(cx);
3252 let snapshot = buffer.snapshot();
3253 let relevant_diagnostics = snapshot
3254 .diagnostics_in_range::<usize, usize>(range.to_offset(&snapshot), false)
3255 .map(|entry| entry.to_lsp_diagnostic_stub())
3256 .collect();
3257 let buffer_id = buffer.remote_id();
3258 let worktree;
3259 let buffer_abs_path;
3260 if let Some(file) = File::from_dyn(buffer.file()) {
3261 worktree = file.worktree.clone();
3262 buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
3263 } else {
3264 return Task::ready(Ok(Default::default()));
3265 };
3266 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3267
3268 if worktree.read(cx).as_local().is_some() {
3269 let buffer_abs_path = buffer_abs_path.unwrap();
3270 let (_, lang_server) = if let Some(server) = self.language_server_for_buffer(buffer, cx)
3271 {
3272 server.clone()
3273 } else {
3274 return Task::ready(Ok(Default::default()));
3275 };
3276
3277 let lsp_range = range_to_lsp(range.to_point_utf16(buffer));
3278 cx.foreground().spawn(async move {
3279 if !lang_server.capabilities().code_action_provider.is_some() {
3280 return Ok(Default::default());
3281 }
3282
3283 Ok(lang_server
3284 .request::<lsp::request::CodeActionRequest>(lsp::CodeActionParams {
3285 text_document: lsp::TextDocumentIdentifier::new(
3286 lsp::Url::from_file_path(buffer_abs_path).unwrap(),
3287 ),
3288 range: lsp_range,
3289 work_done_progress_params: Default::default(),
3290 partial_result_params: Default::default(),
3291 context: lsp::CodeActionContext {
3292 diagnostics: relevant_diagnostics,
3293 only: Some(vec![
3294 lsp::CodeActionKind::QUICKFIX,
3295 lsp::CodeActionKind::REFACTOR,
3296 lsp::CodeActionKind::REFACTOR_EXTRACT,
3297 lsp::CodeActionKind::SOURCE,
3298 ]),
3299 },
3300 })
3301 .await?
3302 .unwrap_or_default()
3303 .into_iter()
3304 .filter_map(|entry| {
3305 if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
3306 Some(CodeAction {
3307 range: range.clone(),
3308 lsp_action,
3309 })
3310 } else {
3311 None
3312 }
3313 })
3314 .collect())
3315 })
3316 } else if let Some(project_id) = self.remote_id() {
3317 let rpc = self.client.clone();
3318 let version = buffer.version();
3319 cx.spawn_weak(|_, mut cx| async move {
3320 let response = rpc
3321 .request(proto::GetCodeActions {
3322 project_id,
3323 buffer_id,
3324 start: Some(language::proto::serialize_anchor(&range.start)),
3325 end: Some(language::proto::serialize_anchor(&range.end)),
3326 version: serialize_version(&version),
3327 })
3328 .await?;
3329
3330 buffer_handle
3331 .update(&mut cx, |buffer, _| {
3332 buffer.wait_for_version(deserialize_version(response.version))
3333 })
3334 .await;
3335
3336 response
3337 .actions
3338 .into_iter()
3339 .map(language::proto::deserialize_code_action)
3340 .collect()
3341 })
3342 } else {
3343 Task::ready(Ok(Default::default()))
3344 }
3345 }
3346
3347 pub fn apply_code_action(
3348 &self,
3349 buffer_handle: ModelHandle<Buffer>,
3350 mut action: CodeAction,
3351 push_to_history: bool,
3352 cx: &mut ModelContext<Self>,
3353 ) -> Task<Result<ProjectTransaction>> {
3354 if self.is_local() {
3355 let buffer = buffer_handle.read(cx);
3356 let (lsp_adapter, lang_server) =
3357 if let Some(server) = self.language_server_for_buffer(buffer, cx) {
3358 server.clone()
3359 } else {
3360 return Task::ready(Ok(Default::default()));
3361 };
3362 let range = action.range.to_point_utf16(buffer);
3363
3364 cx.spawn(|this, mut cx| async move {
3365 if let Some(lsp_range) = action
3366 .lsp_action
3367 .data
3368 .as_mut()
3369 .and_then(|d| d.get_mut("codeActionParams"))
3370 .and_then(|d| d.get_mut("range"))
3371 {
3372 *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap();
3373 action.lsp_action = lang_server
3374 .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
3375 .await?;
3376 } else {
3377 let actions = this
3378 .update(&mut cx, |this, cx| {
3379 this.code_actions(&buffer_handle, action.range, cx)
3380 })
3381 .await?;
3382 action.lsp_action = actions
3383 .into_iter()
3384 .find(|a| a.lsp_action.title == action.lsp_action.title)
3385 .ok_or_else(|| anyhow!("code action is outdated"))?
3386 .lsp_action;
3387 }
3388
3389 if let Some(edit) = action.lsp_action.edit {
3390 Self::deserialize_workspace_edit(
3391 this,
3392 edit,
3393 push_to_history,
3394 lsp_adapter,
3395 lang_server,
3396 &mut cx,
3397 )
3398 .await
3399 } else if let Some(command) = action.lsp_action.command {
3400 this.update(&mut cx, |this, _| {
3401 this.last_workspace_edits_by_language_server
3402 .remove(&lang_server.server_id());
3403 });
3404 lang_server
3405 .request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
3406 command: command.command,
3407 arguments: command.arguments.unwrap_or_default(),
3408 ..Default::default()
3409 })
3410 .await?;
3411 Ok(this.update(&mut cx, |this, _| {
3412 this.last_workspace_edits_by_language_server
3413 .remove(&lang_server.server_id())
3414 .unwrap_or_default()
3415 }))
3416 } else {
3417 Ok(ProjectTransaction::default())
3418 }
3419 })
3420 } else if let Some(project_id) = self.remote_id() {
3421 let client = self.client.clone();
3422 let request = proto::ApplyCodeAction {
3423 project_id,
3424 buffer_id: buffer_handle.read(cx).remote_id(),
3425 action: Some(language::proto::serialize_code_action(&action)),
3426 };
3427 cx.spawn(|this, mut cx| async move {
3428 let response = client
3429 .request(request)
3430 .await?
3431 .transaction
3432 .ok_or_else(|| anyhow!("missing transaction"))?;
3433 this.update(&mut cx, |this, cx| {
3434 this.deserialize_project_transaction(response, push_to_history, cx)
3435 })
3436 .await
3437 })
3438 } else {
3439 Task::ready(Err(anyhow!("project does not have a remote id")))
3440 }
3441 }
3442
3443 async fn deserialize_workspace_edit(
3444 this: ModelHandle<Self>,
3445 edit: lsp::WorkspaceEdit,
3446 push_to_history: bool,
3447 lsp_adapter: Arc<dyn LspAdapter>,
3448 language_server: Arc<LanguageServer>,
3449 cx: &mut AsyncAppContext,
3450 ) -> Result<ProjectTransaction> {
3451 let fs = this.read_with(cx, |this, _| this.fs.clone());
3452 let mut operations = Vec::new();
3453 if let Some(document_changes) = edit.document_changes {
3454 match document_changes {
3455 lsp::DocumentChanges::Edits(edits) => {
3456 operations.extend(edits.into_iter().map(lsp::DocumentChangeOperation::Edit))
3457 }
3458 lsp::DocumentChanges::Operations(ops) => operations = ops,
3459 }
3460 } else if let Some(changes) = edit.changes {
3461 operations.extend(changes.into_iter().map(|(uri, edits)| {
3462 lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
3463 text_document: lsp::OptionalVersionedTextDocumentIdentifier {
3464 uri,
3465 version: None,
3466 },
3467 edits: edits.into_iter().map(lsp::OneOf::Left).collect(),
3468 })
3469 }));
3470 }
3471
3472 let mut project_transaction = ProjectTransaction::default();
3473 for operation in operations {
3474 match operation {
3475 lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Create(op)) => {
3476 let abs_path = op
3477 .uri
3478 .to_file_path()
3479 .map_err(|_| anyhow!("can't convert URI to path"))?;
3480
3481 if let Some(parent_path) = abs_path.parent() {
3482 fs.create_dir(parent_path).await?;
3483 }
3484 if abs_path.ends_with("/") {
3485 fs.create_dir(&abs_path).await?;
3486 } else {
3487 fs.create_file(&abs_path, op.options.map(Into::into).unwrap_or_default())
3488 .await?;
3489 }
3490 }
3491 lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Rename(op)) => {
3492 let source_abs_path = op
3493 .old_uri
3494 .to_file_path()
3495 .map_err(|_| anyhow!("can't convert URI to path"))?;
3496 let target_abs_path = op
3497 .new_uri
3498 .to_file_path()
3499 .map_err(|_| anyhow!("can't convert URI to path"))?;
3500 fs.rename(
3501 &source_abs_path,
3502 &target_abs_path,
3503 op.options.map(Into::into).unwrap_or_default(),
3504 )
3505 .await?;
3506 }
3507 lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Delete(op)) => {
3508 let abs_path = op
3509 .uri
3510 .to_file_path()
3511 .map_err(|_| anyhow!("can't convert URI to path"))?;
3512 let options = op.options.map(Into::into).unwrap_or_default();
3513 if abs_path.ends_with("/") {
3514 fs.remove_dir(&abs_path, options).await?;
3515 } else {
3516 fs.remove_file(&abs_path, options).await?;
3517 }
3518 }
3519 lsp::DocumentChangeOperation::Edit(op) => {
3520 let buffer_to_edit = this
3521 .update(cx, |this, cx| {
3522 this.open_local_buffer_via_lsp(
3523 op.text_document.uri,
3524 lsp_adapter.clone(),
3525 language_server.clone(),
3526 cx,
3527 )
3528 })
3529 .await?;
3530
3531 let edits = this
3532 .update(cx, |this, cx| {
3533 let edits = op.edits.into_iter().map(|edit| match edit {
3534 lsp::OneOf::Left(edit) => edit,
3535 lsp::OneOf::Right(edit) => edit.text_edit,
3536 });
3537 this.edits_from_lsp(
3538 &buffer_to_edit,
3539 edits,
3540 op.text_document.version,
3541 cx,
3542 )
3543 })
3544 .await?;
3545
3546 let transaction = buffer_to_edit.update(cx, |buffer, cx| {
3547 buffer.finalize_last_transaction();
3548 buffer.start_transaction();
3549 for (range, text) in edits {
3550 buffer.edit([(range, text)], cx);
3551 }
3552 let transaction = if buffer.end_transaction(cx).is_some() {
3553 let transaction = buffer.finalize_last_transaction().unwrap().clone();
3554 if !push_to_history {
3555 buffer.forget_transaction(transaction.id);
3556 }
3557 Some(transaction)
3558 } else {
3559 None
3560 };
3561
3562 transaction
3563 });
3564 if let Some(transaction) = transaction {
3565 project_transaction.0.insert(buffer_to_edit, transaction);
3566 }
3567 }
3568 }
3569 }
3570
3571 Ok(project_transaction)
3572 }
3573
3574 pub fn prepare_rename<T: ToPointUtf16>(
3575 &self,
3576 buffer: ModelHandle<Buffer>,
3577 position: T,
3578 cx: &mut ModelContext<Self>,
3579 ) -> Task<Result<Option<Range<Anchor>>>> {
3580 let position = position.to_point_utf16(buffer.read(cx));
3581 self.request_lsp(buffer, PrepareRename { position }, cx)
3582 }
3583
3584 pub fn perform_rename<T: ToPointUtf16>(
3585 &self,
3586 buffer: ModelHandle<Buffer>,
3587 position: T,
3588 new_name: String,
3589 push_to_history: bool,
3590 cx: &mut ModelContext<Self>,
3591 ) -> Task<Result<ProjectTransaction>> {
3592 let position = position.to_point_utf16(buffer.read(cx));
3593 self.request_lsp(
3594 buffer,
3595 PerformRename {
3596 position,
3597 new_name,
3598 push_to_history,
3599 },
3600 cx,
3601 )
3602 }
3603
3604 pub fn search(
3605 &self,
3606 query: SearchQuery,
3607 cx: &mut ModelContext<Self>,
3608 ) -> Task<Result<HashMap<ModelHandle<Buffer>, Vec<Range<Anchor>>>>> {
3609 if self.is_local() {
3610 let snapshots = self
3611 .visible_worktrees(cx)
3612 .filter_map(|tree| {
3613 let tree = tree.read(cx).as_local()?;
3614 Some(tree.snapshot())
3615 })
3616 .collect::<Vec<_>>();
3617
3618 let background = cx.background().clone();
3619 let path_count: usize = snapshots.iter().map(|s| s.visible_file_count()).sum();
3620 if path_count == 0 {
3621 return Task::ready(Ok(Default::default()));
3622 }
3623 let workers = background.num_cpus().min(path_count);
3624 let (matching_paths_tx, mut matching_paths_rx) = smol::channel::bounded(1024);
3625 cx.background()
3626 .spawn({
3627 let fs = self.fs.clone();
3628 let background = cx.background().clone();
3629 let query = query.clone();
3630 async move {
3631 let fs = &fs;
3632 let query = &query;
3633 let matching_paths_tx = &matching_paths_tx;
3634 let paths_per_worker = (path_count + workers - 1) / workers;
3635 let snapshots = &snapshots;
3636 background
3637 .scoped(|scope| {
3638 for worker_ix in 0..workers {
3639 let worker_start_ix = worker_ix * paths_per_worker;
3640 let worker_end_ix = worker_start_ix + paths_per_worker;
3641 scope.spawn(async move {
3642 let mut snapshot_start_ix = 0;
3643 let mut abs_path = PathBuf::new();
3644 for snapshot in snapshots {
3645 let snapshot_end_ix =
3646 snapshot_start_ix + snapshot.visible_file_count();
3647 if worker_end_ix <= snapshot_start_ix {
3648 break;
3649 } else if worker_start_ix > snapshot_end_ix {
3650 snapshot_start_ix = snapshot_end_ix;
3651 continue;
3652 } else {
3653 let start_in_snapshot = worker_start_ix
3654 .saturating_sub(snapshot_start_ix);
3655 let end_in_snapshot =
3656 cmp::min(worker_end_ix, snapshot_end_ix)
3657 - snapshot_start_ix;
3658
3659 for entry in snapshot
3660 .files(false, start_in_snapshot)
3661 .take(end_in_snapshot - start_in_snapshot)
3662 {
3663 if matching_paths_tx.is_closed() {
3664 break;
3665 }
3666
3667 abs_path.clear();
3668 abs_path.push(&snapshot.abs_path());
3669 abs_path.push(&entry.path);
3670 let matches = if let Some(file) =
3671 fs.open_sync(&abs_path).await.log_err()
3672 {
3673 query.detect(file).unwrap_or(false)
3674 } else {
3675 false
3676 };
3677
3678 if matches {
3679 let project_path =
3680 (snapshot.id(), entry.path.clone());
3681 if matching_paths_tx
3682 .send(project_path)
3683 .await
3684 .is_err()
3685 {
3686 break;
3687 }
3688 }
3689 }
3690
3691 snapshot_start_ix = snapshot_end_ix;
3692 }
3693 }
3694 });
3695 }
3696 })
3697 .await;
3698 }
3699 })
3700 .detach();
3701
3702 let (buffers_tx, buffers_rx) = smol::channel::bounded(1024);
3703 let open_buffers = self
3704 .opened_buffers
3705 .values()
3706 .filter_map(|b| b.upgrade(cx))
3707 .collect::<HashSet<_>>();
3708 cx.spawn(|this, cx| async move {
3709 for buffer in &open_buffers {
3710 let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot());
3711 buffers_tx.send((buffer.clone(), snapshot)).await?;
3712 }
3713
3714 let open_buffers = Rc::new(RefCell::new(open_buffers));
3715 while let Some(project_path) = matching_paths_rx.next().await {
3716 if buffers_tx.is_closed() {
3717 break;
3718 }
3719
3720 let this = this.clone();
3721 let open_buffers = open_buffers.clone();
3722 let buffers_tx = buffers_tx.clone();
3723 cx.spawn(|mut cx| async move {
3724 if let Some(buffer) = this
3725 .update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
3726 .await
3727 .log_err()
3728 {
3729 if open_buffers.borrow_mut().insert(buffer.clone()) {
3730 let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot());
3731 buffers_tx.send((buffer, snapshot)).await?;
3732 }
3733 }
3734
3735 Ok::<_, anyhow::Error>(())
3736 })
3737 .detach();
3738 }
3739
3740 Ok::<_, anyhow::Error>(())
3741 })
3742 .detach_and_log_err(cx);
3743
3744 let background = cx.background().clone();
3745 cx.background().spawn(async move {
3746 let query = &query;
3747 let mut matched_buffers = Vec::new();
3748 for _ in 0..workers {
3749 matched_buffers.push(HashMap::default());
3750 }
3751 background
3752 .scoped(|scope| {
3753 for worker_matched_buffers in matched_buffers.iter_mut() {
3754 let mut buffers_rx = buffers_rx.clone();
3755 scope.spawn(async move {
3756 while let Some((buffer, snapshot)) = buffers_rx.next().await {
3757 let buffer_matches = query
3758 .search(snapshot.as_rope())
3759 .await
3760 .iter()
3761 .map(|range| {
3762 snapshot.anchor_before(range.start)
3763 ..snapshot.anchor_after(range.end)
3764 })
3765 .collect::<Vec<_>>();
3766 if !buffer_matches.is_empty() {
3767 worker_matched_buffers
3768 .insert(buffer.clone(), buffer_matches);
3769 }
3770 }
3771 });
3772 }
3773 })
3774 .await;
3775 Ok(matched_buffers.into_iter().flatten().collect())
3776 })
3777 } else if let Some(project_id) = self.remote_id() {
3778 let request = self.client.request(query.to_proto(project_id));
3779 cx.spawn(|this, mut cx| async move {
3780 let response = request.await?;
3781 let mut result = HashMap::default();
3782 for location in response.locations {
3783 let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
3784 let target_buffer = this
3785 .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
3786 .await?;
3787 let start = location
3788 .start
3789 .and_then(deserialize_anchor)
3790 .ok_or_else(|| anyhow!("missing target start"))?;
3791 let end = location
3792 .end
3793 .and_then(deserialize_anchor)
3794 .ok_or_else(|| anyhow!("missing target end"))?;
3795 result
3796 .entry(target_buffer)
3797 .or_insert(Vec::new())
3798 .push(start..end)
3799 }
3800 Ok(result)
3801 })
3802 } else {
3803 Task::ready(Ok(Default::default()))
3804 }
3805 }
3806
3807 fn request_lsp<R: LspCommand>(
3808 &self,
3809 buffer_handle: ModelHandle<Buffer>,
3810 request: R,
3811 cx: &mut ModelContext<Self>,
3812 ) -> Task<Result<R::Response>>
3813 where
3814 <R::LspRequest as lsp::request::Request>::Result: Send,
3815 {
3816 let buffer = buffer_handle.read(cx);
3817 if self.is_local() {
3818 let file = File::from_dyn(buffer.file()).and_then(File::as_local);
3819 if let Some((file, (_, language_server))) =
3820 file.zip(self.language_server_for_buffer(buffer, cx).cloned())
3821 {
3822 let lsp_params = request.to_lsp(&file.abs_path(cx), cx);
3823 return cx.spawn(|this, cx| async move {
3824 if !request.check_capabilities(&language_server.capabilities()) {
3825 return Ok(Default::default());
3826 }
3827
3828 let response = language_server
3829 .request::<R::LspRequest>(lsp_params)
3830 .await
3831 .context("lsp request failed")?;
3832 request
3833 .response_from_lsp(response, this, buffer_handle, cx)
3834 .await
3835 });
3836 }
3837 } else if let Some(project_id) = self.remote_id() {
3838 let rpc = self.client.clone();
3839 let message = request.to_proto(project_id, buffer);
3840 return cx.spawn(|this, cx| async move {
3841 let response = rpc.request(message).await?;
3842 request
3843 .response_from_proto(response, this, buffer_handle, cx)
3844 .await
3845 });
3846 }
3847 Task::ready(Ok(Default::default()))
3848 }
3849
3850 pub fn find_or_create_local_worktree(
3851 &mut self,
3852 abs_path: impl AsRef<Path>,
3853 visible: bool,
3854 cx: &mut ModelContext<Self>,
3855 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
3856 let abs_path = abs_path.as_ref();
3857 if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) {
3858 Task::ready(Ok((tree.clone(), relative_path.into())))
3859 } else {
3860 let worktree = self.create_local_worktree(abs_path, visible, cx);
3861 cx.foreground()
3862 .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
3863 }
3864 }
3865
3866 pub fn find_local_worktree(
3867 &self,
3868 abs_path: &Path,
3869 cx: &AppContext,
3870 ) -> Option<(ModelHandle<Worktree>, PathBuf)> {
3871 for tree in self.worktrees(cx) {
3872 if let Some(relative_path) = tree
3873 .read(cx)
3874 .as_local()
3875 .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
3876 {
3877 return Some((tree.clone(), relative_path.into()));
3878 }
3879 }
3880 None
3881 }
3882
3883 pub fn is_shared(&self) -> bool {
3884 match &self.client_state {
3885 ProjectClientState::Local { is_shared, .. } => *is_shared,
3886 ProjectClientState::Remote { .. } => false,
3887 }
3888 }
3889
3890 fn create_local_worktree(
3891 &mut self,
3892 abs_path: impl AsRef<Path>,
3893 visible: bool,
3894 cx: &mut ModelContext<Self>,
3895 ) -> Task<Result<ModelHandle<Worktree>>> {
3896 let fs = self.fs.clone();
3897 let client = self.client.clone();
3898 let next_entry_id = self.next_entry_id.clone();
3899 let path: Arc<Path> = abs_path.as_ref().into();
3900 let task = self
3901 .loading_local_worktrees
3902 .entry(path.clone())
3903 .or_insert_with(|| {
3904 cx.spawn(|project, mut cx| {
3905 async move {
3906 let worktree = Worktree::local(
3907 client.clone(),
3908 path.clone(),
3909 visible,
3910 fs,
3911 next_entry_id,
3912 &mut cx,
3913 )
3914 .await;
3915 project.update(&mut cx, |project, _| {
3916 project.loading_local_worktrees.remove(&path);
3917 });
3918 let worktree = worktree?;
3919
3920 let project_id = project.update(&mut cx, |project, cx| {
3921 project.add_worktree(&worktree, cx);
3922 project.shared_remote_id()
3923 });
3924
3925 if let Some(project_id) = project_id {
3926 worktree
3927 .update(&mut cx, |worktree, cx| {
3928 worktree.as_local_mut().unwrap().share(project_id, cx)
3929 })
3930 .await
3931 .log_err();
3932 }
3933
3934 Ok(worktree)
3935 }
3936 .map_err(|err| Arc::new(err))
3937 })
3938 .shared()
3939 })
3940 .clone();
3941 cx.foreground().spawn(async move {
3942 match task.await {
3943 Ok(worktree) => Ok(worktree),
3944 Err(err) => Err(anyhow!("{}", err)),
3945 }
3946 })
3947 }
3948
3949 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
3950 self.worktrees.retain(|worktree| {
3951 if let Some(worktree) = worktree.upgrade(cx) {
3952 let id = worktree.read(cx).id();
3953 if id == id_to_remove {
3954 cx.emit(Event::WorktreeRemoved(id));
3955 false
3956 } else {
3957 true
3958 }
3959 } else {
3960 false
3961 }
3962 });
3963 self.metadata_changed(true, cx);
3964 cx.notify();
3965 }
3966
3967 fn add_worktree(&mut self, worktree: &ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
3968 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
3969 if worktree.read(cx).is_local() {
3970 cx.subscribe(&worktree, |this, worktree, _, cx| {
3971 this.update_local_worktree_buffers(worktree, cx);
3972 })
3973 .detach();
3974 }
3975
3976 let push_strong_handle = {
3977 let worktree = worktree.read(cx);
3978 self.is_shared() || worktree.is_visible() || worktree.is_remote()
3979 };
3980 if push_strong_handle {
3981 self.worktrees
3982 .push(WorktreeHandle::Strong(worktree.clone()));
3983 } else {
3984 cx.observe_release(&worktree, |this, _, cx| {
3985 this.worktrees
3986 .retain(|worktree| worktree.upgrade(cx).is_some());
3987 cx.notify();
3988 })
3989 .detach();
3990 self.worktrees
3991 .push(WorktreeHandle::Weak(worktree.downgrade()));
3992 }
3993 self.metadata_changed(true, cx);
3994 cx.emit(Event::WorktreeAdded);
3995 cx.notify();
3996 }
3997
3998 fn update_local_worktree_buffers(
3999 &mut self,
4000 worktree_handle: ModelHandle<Worktree>,
4001 cx: &mut ModelContext<Self>,
4002 ) {
4003 let snapshot = worktree_handle.read(cx).snapshot();
4004 let mut buffers_to_delete = Vec::new();
4005 let mut renamed_buffers = Vec::new();
4006 for (buffer_id, buffer) in &self.opened_buffers {
4007 if let Some(buffer) = buffer.upgrade(cx) {
4008 buffer.update(cx, |buffer, cx| {
4009 if let Some(old_file) = File::from_dyn(buffer.file()) {
4010 if old_file.worktree != worktree_handle {
4011 return;
4012 }
4013
4014 let new_file = if let Some(entry) = old_file
4015 .entry_id
4016 .and_then(|entry_id| snapshot.entry_for_id(entry_id))
4017 {
4018 File {
4019 is_local: true,
4020 entry_id: Some(entry.id),
4021 mtime: entry.mtime,
4022 path: entry.path.clone(),
4023 worktree: worktree_handle.clone(),
4024 }
4025 } else if let Some(entry) =
4026 snapshot.entry_for_path(old_file.path().as_ref())
4027 {
4028 File {
4029 is_local: true,
4030 entry_id: Some(entry.id),
4031 mtime: entry.mtime,
4032 path: entry.path.clone(),
4033 worktree: worktree_handle.clone(),
4034 }
4035 } else {
4036 File {
4037 is_local: true,
4038 entry_id: None,
4039 path: old_file.path().clone(),
4040 mtime: old_file.mtime(),
4041 worktree: worktree_handle.clone(),
4042 }
4043 };
4044
4045 let old_path = old_file.abs_path(cx);
4046 if new_file.abs_path(cx) != old_path {
4047 renamed_buffers.push((cx.handle(), old_path));
4048 }
4049
4050 if let Some(project_id) = self.shared_remote_id() {
4051 self.client
4052 .send(proto::UpdateBufferFile {
4053 project_id,
4054 buffer_id: *buffer_id as u64,
4055 file: Some(new_file.to_proto()),
4056 })
4057 .log_err();
4058 }
4059 buffer.file_updated(Box::new(new_file), cx).detach();
4060 }
4061 });
4062 } else {
4063 buffers_to_delete.push(*buffer_id);
4064 }
4065 }
4066
4067 for buffer_id in buffers_to_delete {
4068 self.opened_buffers.remove(&buffer_id);
4069 }
4070
4071 for (buffer, old_path) in renamed_buffers {
4072 self.unregister_buffer_from_language_server(&buffer, old_path, cx);
4073 self.assign_language_to_buffer(&buffer, cx);
4074 self.register_buffer_with_language_server(&buffer, cx);
4075 }
4076 }
4077
4078 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
4079 let new_active_entry = entry.and_then(|project_path| {
4080 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
4081 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
4082 Some(entry.id)
4083 });
4084 if new_active_entry != self.active_entry {
4085 self.active_entry = new_active_entry;
4086 cx.emit(Event::ActiveEntryChanged(new_active_entry));
4087 }
4088 }
4089
4090 pub fn language_servers_running_disk_based_diagnostics<'a>(
4091 &'a self,
4092 ) -> impl 'a + Iterator<Item = usize> {
4093 self.language_server_statuses
4094 .iter()
4095 .filter_map(|(id, status)| {
4096 if status.pending_diagnostic_updates > 0 {
4097 Some(*id)
4098 } else {
4099 None
4100 }
4101 })
4102 }
4103
4104 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
4105 let mut summary = DiagnosticSummary::default();
4106 for (_, path_summary) in self.diagnostic_summaries(cx) {
4107 summary.error_count += path_summary.error_count;
4108 summary.warning_count += path_summary.warning_count;
4109 }
4110 summary
4111 }
4112
4113 pub fn diagnostic_summaries<'a>(
4114 &'a self,
4115 cx: &'a AppContext,
4116 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
4117 self.worktrees(cx).flat_map(move |worktree| {
4118 let worktree = worktree.read(cx);
4119 let worktree_id = worktree.id();
4120 worktree
4121 .diagnostic_summaries()
4122 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
4123 })
4124 }
4125
4126 pub fn disk_based_diagnostics_started(
4127 &mut self,
4128 language_server_id: usize,
4129 cx: &mut ModelContext<Self>,
4130 ) {
4131 cx.emit(Event::DiskBasedDiagnosticsStarted { language_server_id });
4132 }
4133
4134 pub fn disk_based_diagnostics_finished(
4135 &mut self,
4136 language_server_id: usize,
4137 cx: &mut ModelContext<Self>,
4138 ) {
4139 cx.emit(Event::DiskBasedDiagnosticsFinished { language_server_id });
4140 }
4141
4142 pub fn active_entry(&self) -> Option<ProjectEntryId> {
4143 self.active_entry
4144 }
4145
4146 pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<ProjectEntryId> {
4147 self.worktree_for_id(path.worktree_id, cx)?
4148 .read(cx)
4149 .entry_for_path(&path.path)
4150 .map(|entry| entry.id)
4151 }
4152
4153 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option<ProjectPath> {
4154 let worktree = self.worktree_for_entry(entry_id, cx)?;
4155 let worktree = worktree.read(cx);
4156 let worktree_id = worktree.id();
4157 let path = worktree.entry_for_id(entry_id)?.path.clone();
4158 Some(ProjectPath { worktree_id, path })
4159 }
4160
4161 // RPC message handlers
4162
4163 async fn handle_request_join_project(
4164 this: ModelHandle<Self>,
4165 message: TypedEnvelope<proto::RequestJoinProject>,
4166 _: Arc<Client>,
4167 mut cx: AsyncAppContext,
4168 ) -> Result<()> {
4169 let user_id = message.payload.requester_id;
4170 if this.read_with(&cx, |project, _| {
4171 project.collaborators.values().any(|c| c.user.id == user_id)
4172 }) {
4173 this.update(&mut cx, |this, cx| {
4174 this.respond_to_join_request(user_id, true, cx)
4175 });
4176 } else {
4177 let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
4178 let user = user_store
4179 .update(&mut cx, |store, cx| store.fetch_user(user_id, cx))
4180 .await?;
4181 this.update(&mut cx, |_, cx| cx.emit(Event::ContactRequestedJoin(user)));
4182 }
4183 Ok(())
4184 }
4185
4186 async fn handle_unregister_project(
4187 this: ModelHandle<Self>,
4188 _: TypedEnvelope<proto::UnregisterProject>,
4189 _: Arc<Client>,
4190 mut cx: AsyncAppContext,
4191 ) -> Result<()> {
4192 this.update(&mut cx, |this, cx| this.removed_from_project(cx));
4193 Ok(())
4194 }
4195
4196 async fn handle_project_unshared(
4197 this: ModelHandle<Self>,
4198 _: TypedEnvelope<proto::ProjectUnshared>,
4199 _: Arc<Client>,
4200 mut cx: AsyncAppContext,
4201 ) -> Result<()> {
4202 this.update(&mut cx, |this, cx| this.unshared(cx));
4203 Ok(())
4204 }
4205
4206 async fn handle_add_collaborator(
4207 this: ModelHandle<Self>,
4208 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
4209 _: Arc<Client>,
4210 mut cx: AsyncAppContext,
4211 ) -> Result<()> {
4212 let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
4213 let collaborator = envelope
4214 .payload
4215 .collaborator
4216 .take()
4217 .ok_or_else(|| anyhow!("empty collaborator"))?;
4218
4219 let collaborator = Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
4220 this.update(&mut cx, |this, cx| {
4221 this.collaborators
4222 .insert(collaborator.peer_id, collaborator);
4223 cx.notify();
4224 });
4225
4226 Ok(())
4227 }
4228
4229 async fn handle_remove_collaborator(
4230 this: ModelHandle<Self>,
4231 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
4232 _: Arc<Client>,
4233 mut cx: AsyncAppContext,
4234 ) -> Result<()> {
4235 this.update(&mut cx, |this, cx| {
4236 let peer_id = PeerId(envelope.payload.peer_id);
4237 let replica_id = this
4238 .collaborators
4239 .remove(&peer_id)
4240 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
4241 .replica_id;
4242 for (_, buffer) in &this.opened_buffers {
4243 if let Some(buffer) = buffer.upgrade(cx) {
4244 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
4245 }
4246 }
4247
4248 cx.emit(Event::CollaboratorLeft(peer_id));
4249 cx.notify();
4250 Ok(())
4251 })
4252 }
4253
4254 async fn handle_join_project_request_cancelled(
4255 this: ModelHandle<Self>,
4256 envelope: TypedEnvelope<proto::JoinProjectRequestCancelled>,
4257 _: Arc<Client>,
4258 mut cx: AsyncAppContext,
4259 ) -> Result<()> {
4260 let user = this
4261 .update(&mut cx, |this, cx| {
4262 this.user_store.update(cx, |user_store, cx| {
4263 user_store.fetch_user(envelope.payload.requester_id, cx)
4264 })
4265 })
4266 .await?;
4267
4268 this.update(&mut cx, |_, cx| {
4269 cx.emit(Event::ContactCancelledJoinRequest(user));
4270 });
4271
4272 Ok(())
4273 }
4274
4275 async fn handle_update_project(
4276 this: ModelHandle<Self>,
4277 envelope: TypedEnvelope<proto::UpdateProject>,
4278 client: Arc<Client>,
4279 mut cx: AsyncAppContext,
4280 ) -> Result<()> {
4281 this.update(&mut cx, |this, cx| {
4282 let replica_id = this.replica_id();
4283 let remote_id = this.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
4284
4285 let mut old_worktrees_by_id = this
4286 .worktrees
4287 .drain(..)
4288 .filter_map(|worktree| {
4289 let worktree = worktree.upgrade(cx)?;
4290 Some((worktree.read(cx).id(), worktree))
4291 })
4292 .collect::<HashMap<_, _>>();
4293
4294 for worktree in envelope.payload.worktrees {
4295 if let Some(old_worktree) =
4296 old_worktrees_by_id.remove(&WorktreeId::from_proto(worktree.id))
4297 {
4298 this.worktrees.push(WorktreeHandle::Strong(old_worktree));
4299 } else {
4300 let worktree = proto::Worktree {
4301 id: worktree.id,
4302 root_name: worktree.root_name,
4303 entries: Default::default(),
4304 diagnostic_summaries: Default::default(),
4305 visible: worktree.visible,
4306 scan_id: 0,
4307 };
4308 let (worktree, load_task) =
4309 Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx);
4310 this.add_worktree(&worktree, cx);
4311 load_task.detach();
4312 }
4313 }
4314
4315 this.metadata_changed(true, cx);
4316 for (id, _) in old_worktrees_by_id {
4317 cx.emit(Event::WorktreeRemoved(id));
4318 }
4319
4320 Ok(())
4321 })
4322 }
4323
4324 async fn handle_update_worktree(
4325 this: ModelHandle<Self>,
4326 envelope: TypedEnvelope<proto::UpdateWorktree>,
4327 _: Arc<Client>,
4328 mut cx: AsyncAppContext,
4329 ) -> Result<()> {
4330 this.update(&mut cx, |this, cx| {
4331 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4332 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
4333 worktree.update(cx, |worktree, _| {
4334 let worktree = worktree.as_remote_mut().unwrap();
4335 worktree.update_from_remote(envelope)
4336 })?;
4337 }
4338 Ok(())
4339 })
4340 }
4341
4342 async fn handle_create_project_entry(
4343 this: ModelHandle<Self>,
4344 envelope: TypedEnvelope<proto::CreateProjectEntry>,
4345 _: Arc<Client>,
4346 mut cx: AsyncAppContext,
4347 ) -> Result<proto::ProjectEntryResponse> {
4348 let worktree = this.update(&mut cx, |this, cx| {
4349 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4350 this.worktree_for_id(worktree_id, cx)
4351 .ok_or_else(|| anyhow!("worktree not found"))
4352 })?;
4353 let worktree_scan_id = worktree.read_with(&cx, |worktree, _| worktree.scan_id());
4354 let entry = worktree
4355 .update(&mut cx, |worktree, cx| {
4356 let worktree = worktree.as_local_mut().unwrap();
4357 let path = PathBuf::from(OsString::from_vec(envelope.payload.path));
4358 worktree.create_entry(path, envelope.payload.is_directory, cx)
4359 })
4360 .await?;
4361 Ok(proto::ProjectEntryResponse {
4362 entry: Some((&entry).into()),
4363 worktree_scan_id: worktree_scan_id as u64,
4364 })
4365 }
4366
4367 async fn handle_rename_project_entry(
4368 this: ModelHandle<Self>,
4369 envelope: TypedEnvelope<proto::RenameProjectEntry>,
4370 _: Arc<Client>,
4371 mut cx: AsyncAppContext,
4372 ) -> Result<proto::ProjectEntryResponse> {
4373 let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
4374 let worktree = this.read_with(&cx, |this, cx| {
4375 this.worktree_for_entry(entry_id, cx)
4376 .ok_or_else(|| anyhow!("worktree not found"))
4377 })?;
4378 let worktree_scan_id = worktree.read_with(&cx, |worktree, _| worktree.scan_id());
4379 let entry = worktree
4380 .update(&mut cx, |worktree, cx| {
4381 let new_path = PathBuf::from(OsString::from_vec(envelope.payload.new_path));
4382 worktree
4383 .as_local_mut()
4384 .unwrap()
4385 .rename_entry(entry_id, new_path, cx)
4386 .ok_or_else(|| anyhow!("invalid entry"))
4387 })?
4388 .await?;
4389 Ok(proto::ProjectEntryResponse {
4390 entry: Some((&entry).into()),
4391 worktree_scan_id: worktree_scan_id as u64,
4392 })
4393 }
4394
4395 async fn handle_copy_project_entry(
4396 this: ModelHandle<Self>,
4397 envelope: TypedEnvelope<proto::CopyProjectEntry>,
4398 _: Arc<Client>,
4399 mut cx: AsyncAppContext,
4400 ) -> Result<proto::ProjectEntryResponse> {
4401 let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
4402 let worktree = this.read_with(&cx, |this, cx| {
4403 this.worktree_for_entry(entry_id, cx)
4404 .ok_or_else(|| anyhow!("worktree not found"))
4405 })?;
4406 let worktree_scan_id = worktree.read_with(&cx, |worktree, _| worktree.scan_id());
4407 let entry = worktree
4408 .update(&mut cx, |worktree, cx| {
4409 let new_path = PathBuf::from(OsString::from_vec(envelope.payload.new_path));
4410 worktree
4411 .as_local_mut()
4412 .unwrap()
4413 .copy_entry(entry_id, new_path, cx)
4414 .ok_or_else(|| anyhow!("invalid entry"))
4415 })?
4416 .await?;
4417 Ok(proto::ProjectEntryResponse {
4418 entry: Some((&entry).into()),
4419 worktree_scan_id: worktree_scan_id as u64,
4420 })
4421 }
4422
4423 async fn handle_delete_project_entry(
4424 this: ModelHandle<Self>,
4425 envelope: TypedEnvelope<proto::DeleteProjectEntry>,
4426 _: Arc<Client>,
4427 mut cx: AsyncAppContext,
4428 ) -> Result<proto::ProjectEntryResponse> {
4429 let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
4430 let worktree = this.read_with(&cx, |this, cx| {
4431 this.worktree_for_entry(entry_id, cx)
4432 .ok_or_else(|| anyhow!("worktree not found"))
4433 })?;
4434 let worktree_scan_id = worktree.read_with(&cx, |worktree, _| worktree.scan_id());
4435 worktree
4436 .update(&mut cx, |worktree, cx| {
4437 worktree
4438 .as_local_mut()
4439 .unwrap()
4440 .delete_entry(entry_id, cx)
4441 .ok_or_else(|| anyhow!("invalid entry"))
4442 })?
4443 .await?;
4444 Ok(proto::ProjectEntryResponse {
4445 entry: None,
4446 worktree_scan_id: worktree_scan_id as u64,
4447 })
4448 }
4449
4450 async fn handle_update_diagnostic_summary(
4451 this: ModelHandle<Self>,
4452 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
4453 _: Arc<Client>,
4454 mut cx: AsyncAppContext,
4455 ) -> Result<()> {
4456 this.update(&mut cx, |this, cx| {
4457 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4458 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
4459 if let Some(summary) = envelope.payload.summary {
4460 let project_path = ProjectPath {
4461 worktree_id,
4462 path: Path::new(&summary.path).into(),
4463 };
4464 worktree.update(cx, |worktree, _| {
4465 worktree
4466 .as_remote_mut()
4467 .unwrap()
4468 .update_diagnostic_summary(project_path.path.clone(), &summary);
4469 });
4470 cx.emit(Event::DiagnosticsUpdated {
4471 language_server_id: summary.language_server_id as usize,
4472 path: project_path,
4473 });
4474 }
4475 }
4476 Ok(())
4477 })
4478 }
4479
4480 async fn handle_start_language_server(
4481 this: ModelHandle<Self>,
4482 envelope: TypedEnvelope<proto::StartLanguageServer>,
4483 _: Arc<Client>,
4484 mut cx: AsyncAppContext,
4485 ) -> Result<()> {
4486 let server = envelope
4487 .payload
4488 .server
4489 .ok_or_else(|| anyhow!("invalid server"))?;
4490 this.update(&mut cx, |this, cx| {
4491 this.language_server_statuses.insert(
4492 server.id as usize,
4493 LanguageServerStatus {
4494 name: server.name,
4495 pending_work: Default::default(),
4496 pending_diagnostic_updates: 0,
4497 },
4498 );
4499 cx.notify();
4500 });
4501 Ok(())
4502 }
4503
4504 async fn handle_update_language_server(
4505 this: ModelHandle<Self>,
4506 envelope: TypedEnvelope<proto::UpdateLanguageServer>,
4507 _: Arc<Client>,
4508 mut cx: AsyncAppContext,
4509 ) -> Result<()> {
4510 let language_server_id = envelope.payload.language_server_id as usize;
4511 match envelope
4512 .payload
4513 .variant
4514 .ok_or_else(|| anyhow!("invalid variant"))?
4515 {
4516 proto::update_language_server::Variant::WorkStart(payload) => {
4517 this.update(&mut cx, |this, cx| {
4518 this.on_lsp_work_start(
4519 language_server_id,
4520 payload.token,
4521 LanguageServerProgress {
4522 message: payload.message,
4523 percentage: payload.percentage.map(|p| p as usize),
4524 last_update_at: Instant::now(),
4525 },
4526 cx,
4527 );
4528 })
4529 }
4530 proto::update_language_server::Variant::WorkProgress(payload) => {
4531 this.update(&mut cx, |this, cx| {
4532 this.on_lsp_work_progress(
4533 language_server_id,
4534 payload.token,
4535 LanguageServerProgress {
4536 message: payload.message,
4537 percentage: payload.percentage.map(|p| p as usize),
4538 last_update_at: Instant::now(),
4539 },
4540 cx,
4541 );
4542 })
4543 }
4544 proto::update_language_server::Variant::WorkEnd(payload) => {
4545 this.update(&mut cx, |this, cx| {
4546 this.on_lsp_work_end(language_server_id, payload.token, cx);
4547 })
4548 }
4549 proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(_) => {
4550 this.update(&mut cx, |this, cx| {
4551 this.disk_based_diagnostics_started(language_server_id, cx);
4552 })
4553 }
4554 proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(_) => {
4555 this.update(&mut cx, |this, cx| {
4556 this.disk_based_diagnostics_finished(language_server_id, cx)
4557 });
4558 }
4559 }
4560
4561 Ok(())
4562 }
4563
4564 async fn handle_update_buffer(
4565 this: ModelHandle<Self>,
4566 envelope: TypedEnvelope<proto::UpdateBuffer>,
4567 _: Arc<Client>,
4568 mut cx: AsyncAppContext,
4569 ) -> Result<()> {
4570 this.update(&mut cx, |this, cx| {
4571 let payload = envelope.payload.clone();
4572 let buffer_id = payload.buffer_id;
4573 let ops = payload
4574 .operations
4575 .into_iter()
4576 .map(|op| language::proto::deserialize_operation(op))
4577 .collect::<Result<Vec<_>, _>>()?;
4578 let is_remote = this.is_remote();
4579 match this.opened_buffers.entry(buffer_id) {
4580 hash_map::Entry::Occupied(mut e) => match e.get_mut() {
4581 OpenBuffer::Strong(buffer) => {
4582 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
4583 }
4584 OpenBuffer::Loading(operations) => operations.extend_from_slice(&ops),
4585 OpenBuffer::Weak(_) => {}
4586 },
4587 hash_map::Entry::Vacant(e) => {
4588 assert!(
4589 is_remote,
4590 "received buffer update from {:?}",
4591 envelope.original_sender_id
4592 );
4593 e.insert(OpenBuffer::Loading(ops));
4594 }
4595 }
4596 Ok(())
4597 })
4598 }
4599
4600 async fn handle_update_buffer_file(
4601 this: ModelHandle<Self>,
4602 envelope: TypedEnvelope<proto::UpdateBufferFile>,
4603 _: Arc<Client>,
4604 mut cx: AsyncAppContext,
4605 ) -> Result<()> {
4606 this.update(&mut cx, |this, cx| {
4607 let payload = envelope.payload.clone();
4608 let buffer_id = payload.buffer_id;
4609 let file = payload.file.ok_or_else(|| anyhow!("invalid file"))?;
4610 let worktree = this
4611 .worktree_for_id(WorktreeId::from_proto(file.worktree_id), cx)
4612 .ok_or_else(|| anyhow!("no such worktree"))?;
4613 let file = File::from_proto(file, worktree.clone(), cx)?;
4614 let buffer = this
4615 .opened_buffers
4616 .get_mut(&buffer_id)
4617 .and_then(|b| b.upgrade(cx))
4618 .ok_or_else(|| anyhow!("no such buffer"))?;
4619 buffer.update(cx, |buffer, cx| {
4620 buffer.file_updated(Box::new(file), cx).detach();
4621 });
4622 Ok(())
4623 })
4624 }
4625
4626 async fn handle_save_buffer(
4627 this: ModelHandle<Self>,
4628 envelope: TypedEnvelope<proto::SaveBuffer>,
4629 _: Arc<Client>,
4630 mut cx: AsyncAppContext,
4631 ) -> Result<proto::BufferSaved> {
4632 let buffer_id = envelope.payload.buffer_id;
4633 let requested_version = deserialize_version(envelope.payload.version);
4634
4635 let (project_id, buffer) = this.update(&mut cx, |this, cx| {
4636 let project_id = this.remote_id().ok_or_else(|| anyhow!("not connected"))?;
4637 let buffer = this
4638 .opened_buffers
4639 .get(&buffer_id)
4640 .and_then(|buffer| buffer.upgrade(cx))
4641 .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?;
4642 Ok::<_, anyhow::Error>((project_id, buffer))
4643 })?;
4644 buffer
4645 .update(&mut cx, |buffer, _| {
4646 buffer.wait_for_version(requested_version)
4647 })
4648 .await;
4649
4650 let (saved_version, mtime) = buffer.update(&mut cx, |buffer, cx| buffer.save(cx)).await?;
4651 Ok(proto::BufferSaved {
4652 project_id,
4653 buffer_id,
4654 version: serialize_version(&saved_version),
4655 mtime: Some(mtime.into()),
4656 })
4657 }
4658
4659 async fn handle_reload_buffers(
4660 this: ModelHandle<Self>,
4661 envelope: TypedEnvelope<proto::ReloadBuffers>,
4662 _: Arc<Client>,
4663 mut cx: AsyncAppContext,
4664 ) -> Result<proto::ReloadBuffersResponse> {
4665 let sender_id = envelope.original_sender_id()?;
4666 let reload = this.update(&mut cx, |this, cx| {
4667 let mut buffers = HashSet::default();
4668 for buffer_id in &envelope.payload.buffer_ids {
4669 buffers.insert(
4670 this.opened_buffers
4671 .get(buffer_id)
4672 .and_then(|buffer| buffer.upgrade(cx))
4673 .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
4674 );
4675 }
4676 Ok::<_, anyhow::Error>(this.reload_buffers(buffers, false, cx))
4677 })?;
4678
4679 let project_transaction = reload.await?;
4680 let project_transaction = this.update(&mut cx, |this, cx| {
4681 this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
4682 });
4683 Ok(proto::ReloadBuffersResponse {
4684 transaction: Some(project_transaction),
4685 })
4686 }
4687
4688 async fn handle_format_buffers(
4689 this: ModelHandle<Self>,
4690 envelope: TypedEnvelope<proto::FormatBuffers>,
4691 _: Arc<Client>,
4692 mut cx: AsyncAppContext,
4693 ) -> Result<proto::FormatBuffersResponse> {
4694 let sender_id = envelope.original_sender_id()?;
4695 let format = this.update(&mut cx, |this, cx| {
4696 let mut buffers = HashSet::default();
4697 for buffer_id in &envelope.payload.buffer_ids {
4698 buffers.insert(
4699 this.opened_buffers
4700 .get(buffer_id)
4701 .and_then(|buffer| buffer.upgrade(cx))
4702 .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
4703 );
4704 }
4705 Ok::<_, anyhow::Error>(this.format(buffers, false, cx))
4706 })?;
4707
4708 let project_transaction = format.await?;
4709 let project_transaction = this.update(&mut cx, |this, cx| {
4710 this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
4711 });
4712 Ok(proto::FormatBuffersResponse {
4713 transaction: Some(project_transaction),
4714 })
4715 }
4716
4717 async fn handle_get_completions(
4718 this: ModelHandle<Self>,
4719 envelope: TypedEnvelope<proto::GetCompletions>,
4720 _: Arc<Client>,
4721 mut cx: AsyncAppContext,
4722 ) -> Result<proto::GetCompletionsResponse> {
4723 let position = envelope
4724 .payload
4725 .position
4726 .and_then(language::proto::deserialize_anchor)
4727 .ok_or_else(|| anyhow!("invalid position"))?;
4728 let version = deserialize_version(envelope.payload.version);
4729 let buffer = this.read_with(&cx, |this, cx| {
4730 this.opened_buffers
4731 .get(&envelope.payload.buffer_id)
4732 .and_then(|buffer| buffer.upgrade(cx))
4733 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
4734 })?;
4735 buffer
4736 .update(&mut cx, |buffer, _| buffer.wait_for_version(version))
4737 .await;
4738 let version = buffer.read_with(&cx, |buffer, _| buffer.version());
4739 let completions = this
4740 .update(&mut cx, |this, cx| this.completions(&buffer, position, cx))
4741 .await?;
4742
4743 Ok(proto::GetCompletionsResponse {
4744 completions: completions
4745 .iter()
4746 .map(language::proto::serialize_completion)
4747 .collect(),
4748 version: serialize_version(&version),
4749 })
4750 }
4751
4752 async fn handle_apply_additional_edits_for_completion(
4753 this: ModelHandle<Self>,
4754 envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
4755 _: Arc<Client>,
4756 mut cx: AsyncAppContext,
4757 ) -> Result<proto::ApplyCompletionAdditionalEditsResponse> {
4758 let apply_additional_edits = this.update(&mut cx, |this, cx| {
4759 let buffer = this
4760 .opened_buffers
4761 .get(&envelope.payload.buffer_id)
4762 .and_then(|buffer| buffer.upgrade(cx))
4763 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
4764 let language = buffer.read(cx).language();
4765 let completion = language::proto::deserialize_completion(
4766 envelope
4767 .payload
4768 .completion
4769 .ok_or_else(|| anyhow!("invalid completion"))?,
4770 language,
4771 )?;
4772 Ok::<_, anyhow::Error>(
4773 this.apply_additional_edits_for_completion(buffer, completion, false, cx),
4774 )
4775 })?;
4776
4777 Ok(proto::ApplyCompletionAdditionalEditsResponse {
4778 transaction: apply_additional_edits
4779 .await?
4780 .as_ref()
4781 .map(language::proto::serialize_transaction),
4782 })
4783 }
4784
4785 async fn handle_get_code_actions(
4786 this: ModelHandle<Self>,
4787 envelope: TypedEnvelope<proto::GetCodeActions>,
4788 _: Arc<Client>,
4789 mut cx: AsyncAppContext,
4790 ) -> Result<proto::GetCodeActionsResponse> {
4791 let start = envelope
4792 .payload
4793 .start
4794 .and_then(language::proto::deserialize_anchor)
4795 .ok_or_else(|| anyhow!("invalid start"))?;
4796 let end = envelope
4797 .payload
4798 .end
4799 .and_then(language::proto::deserialize_anchor)
4800 .ok_or_else(|| anyhow!("invalid end"))?;
4801 let buffer = this.update(&mut cx, |this, cx| {
4802 this.opened_buffers
4803 .get(&envelope.payload.buffer_id)
4804 .and_then(|buffer| buffer.upgrade(cx))
4805 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
4806 })?;
4807 buffer
4808 .update(&mut cx, |buffer, _| {
4809 buffer.wait_for_version(deserialize_version(envelope.payload.version))
4810 })
4811 .await;
4812
4813 let version = buffer.read_with(&cx, |buffer, _| buffer.version());
4814 let code_actions = this.update(&mut cx, |this, cx| {
4815 Ok::<_, anyhow::Error>(this.code_actions(&buffer, start..end, cx))
4816 })?;
4817
4818 Ok(proto::GetCodeActionsResponse {
4819 actions: code_actions
4820 .await?
4821 .iter()
4822 .map(language::proto::serialize_code_action)
4823 .collect(),
4824 version: serialize_version(&version),
4825 })
4826 }
4827
4828 async fn handle_apply_code_action(
4829 this: ModelHandle<Self>,
4830 envelope: TypedEnvelope<proto::ApplyCodeAction>,
4831 _: Arc<Client>,
4832 mut cx: AsyncAppContext,
4833 ) -> Result<proto::ApplyCodeActionResponse> {
4834 let sender_id = envelope.original_sender_id()?;
4835 let action = language::proto::deserialize_code_action(
4836 envelope
4837 .payload
4838 .action
4839 .ok_or_else(|| anyhow!("invalid action"))?,
4840 )?;
4841 let apply_code_action = this.update(&mut cx, |this, cx| {
4842 let buffer = this
4843 .opened_buffers
4844 .get(&envelope.payload.buffer_id)
4845 .and_then(|buffer| buffer.upgrade(cx))
4846 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
4847 Ok::<_, anyhow::Error>(this.apply_code_action(buffer, action, false, cx))
4848 })?;
4849
4850 let project_transaction = apply_code_action.await?;
4851 let project_transaction = this.update(&mut cx, |this, cx| {
4852 this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
4853 });
4854 Ok(proto::ApplyCodeActionResponse {
4855 transaction: Some(project_transaction),
4856 })
4857 }
4858
4859 async fn handle_lsp_command<T: LspCommand>(
4860 this: ModelHandle<Self>,
4861 envelope: TypedEnvelope<T::ProtoRequest>,
4862 _: Arc<Client>,
4863 mut cx: AsyncAppContext,
4864 ) -> Result<<T::ProtoRequest as proto::RequestMessage>::Response>
4865 where
4866 <T::LspRequest as lsp::request::Request>::Result: Send,
4867 {
4868 let sender_id = envelope.original_sender_id()?;
4869 let buffer_id = T::buffer_id_from_proto(&envelope.payload);
4870 let buffer_handle = this.read_with(&cx, |this, _| {
4871 this.opened_buffers
4872 .get(&buffer_id)
4873 .and_then(|buffer| buffer.upgrade(&cx))
4874 .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))
4875 })?;
4876 let request = T::from_proto(
4877 envelope.payload,
4878 this.clone(),
4879 buffer_handle.clone(),
4880 cx.clone(),
4881 )
4882 .await?;
4883 let buffer_version = buffer_handle.read_with(&cx, |buffer, _| buffer.version());
4884 let response = this
4885 .update(&mut cx, |this, cx| {
4886 this.request_lsp(buffer_handle, request, cx)
4887 })
4888 .await?;
4889 this.update(&mut cx, |this, cx| {
4890 Ok(T::response_to_proto(
4891 response,
4892 this,
4893 sender_id,
4894 &buffer_version,
4895 cx,
4896 ))
4897 })
4898 }
4899
4900 async fn handle_get_project_symbols(
4901 this: ModelHandle<Self>,
4902 envelope: TypedEnvelope<proto::GetProjectSymbols>,
4903 _: Arc<Client>,
4904 mut cx: AsyncAppContext,
4905 ) -> Result<proto::GetProjectSymbolsResponse> {
4906 let symbols = this
4907 .update(&mut cx, |this, cx| {
4908 this.symbols(&envelope.payload.query, cx)
4909 })
4910 .await?;
4911
4912 Ok(proto::GetProjectSymbolsResponse {
4913 symbols: symbols.iter().map(serialize_symbol).collect(),
4914 })
4915 }
4916
4917 async fn handle_search_project(
4918 this: ModelHandle<Self>,
4919 envelope: TypedEnvelope<proto::SearchProject>,
4920 _: Arc<Client>,
4921 mut cx: AsyncAppContext,
4922 ) -> Result<proto::SearchProjectResponse> {
4923 let peer_id = envelope.original_sender_id()?;
4924 let query = SearchQuery::from_proto(envelope.payload)?;
4925 let result = this
4926 .update(&mut cx, |this, cx| this.search(query, cx))
4927 .await?;
4928
4929 this.update(&mut cx, |this, cx| {
4930 let mut locations = Vec::new();
4931 for (buffer, ranges) in result {
4932 for range in ranges {
4933 let start = serialize_anchor(&range.start);
4934 let end = serialize_anchor(&range.end);
4935 let buffer = this.serialize_buffer_for_peer(&buffer, peer_id, cx);
4936 locations.push(proto::Location {
4937 buffer: Some(buffer),
4938 start: Some(start),
4939 end: Some(end),
4940 });
4941 }
4942 }
4943 Ok(proto::SearchProjectResponse { locations })
4944 })
4945 }
4946
4947 async fn handle_open_buffer_for_symbol(
4948 this: ModelHandle<Self>,
4949 envelope: TypedEnvelope<proto::OpenBufferForSymbol>,
4950 _: Arc<Client>,
4951 mut cx: AsyncAppContext,
4952 ) -> Result<proto::OpenBufferForSymbolResponse> {
4953 let peer_id = envelope.original_sender_id()?;
4954 let symbol = envelope
4955 .payload
4956 .symbol
4957 .ok_or_else(|| anyhow!("invalid symbol"))?;
4958 let symbol = this.read_with(&cx, |this, _| {
4959 let symbol = this.deserialize_symbol(symbol)?;
4960 let signature = this.symbol_signature(symbol.worktree_id, &symbol.path);
4961 if signature == symbol.signature {
4962 Ok(symbol)
4963 } else {
4964 Err(anyhow!("invalid symbol signature"))
4965 }
4966 })?;
4967 let buffer = this
4968 .update(&mut cx, |this, cx| this.open_buffer_for_symbol(&symbol, cx))
4969 .await?;
4970
4971 Ok(proto::OpenBufferForSymbolResponse {
4972 buffer: Some(this.update(&mut cx, |this, cx| {
4973 this.serialize_buffer_for_peer(&buffer, peer_id, cx)
4974 })),
4975 })
4976 }
4977
4978 fn symbol_signature(&self, worktree_id: WorktreeId, path: &Path) -> [u8; 32] {
4979 let mut hasher = Sha256::new();
4980 hasher.update(worktree_id.to_proto().to_be_bytes());
4981 hasher.update(path.to_string_lossy().as_bytes());
4982 hasher.update(self.nonce.to_be_bytes());
4983 hasher.finalize().as_slice().try_into().unwrap()
4984 }
4985
4986 async fn handle_open_buffer_by_id(
4987 this: ModelHandle<Self>,
4988 envelope: TypedEnvelope<proto::OpenBufferById>,
4989 _: Arc<Client>,
4990 mut cx: AsyncAppContext,
4991 ) -> Result<proto::OpenBufferResponse> {
4992 let peer_id = envelope.original_sender_id()?;
4993 let buffer = this
4994 .update(&mut cx, |this, cx| {
4995 this.open_buffer_by_id(envelope.payload.id, cx)
4996 })
4997 .await?;
4998 this.update(&mut cx, |this, cx| {
4999 Ok(proto::OpenBufferResponse {
5000 buffer: Some(this.serialize_buffer_for_peer(&buffer, peer_id, cx)),
5001 })
5002 })
5003 }
5004
5005 async fn handle_open_buffer_by_path(
5006 this: ModelHandle<Self>,
5007 envelope: TypedEnvelope<proto::OpenBufferByPath>,
5008 _: Arc<Client>,
5009 mut cx: AsyncAppContext,
5010 ) -> Result<proto::OpenBufferResponse> {
5011 let peer_id = envelope.original_sender_id()?;
5012 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
5013 let open_buffer = this.update(&mut cx, |this, cx| {
5014 this.open_buffer(
5015 ProjectPath {
5016 worktree_id,
5017 path: PathBuf::from(envelope.payload.path).into(),
5018 },
5019 cx,
5020 )
5021 });
5022
5023 let buffer = open_buffer.await?;
5024 this.update(&mut cx, |this, cx| {
5025 Ok(proto::OpenBufferResponse {
5026 buffer: Some(this.serialize_buffer_for_peer(&buffer, peer_id, cx)),
5027 })
5028 })
5029 }
5030
5031 fn serialize_project_transaction_for_peer(
5032 &mut self,
5033 project_transaction: ProjectTransaction,
5034 peer_id: PeerId,
5035 cx: &AppContext,
5036 ) -> proto::ProjectTransaction {
5037 let mut serialized_transaction = proto::ProjectTransaction {
5038 buffers: Default::default(),
5039 transactions: Default::default(),
5040 };
5041 for (buffer, transaction) in project_transaction.0 {
5042 serialized_transaction
5043 .buffers
5044 .push(self.serialize_buffer_for_peer(&buffer, peer_id, cx));
5045 serialized_transaction
5046 .transactions
5047 .push(language::proto::serialize_transaction(&transaction));
5048 }
5049 serialized_transaction
5050 }
5051
5052 fn deserialize_project_transaction(
5053 &mut self,
5054 message: proto::ProjectTransaction,
5055 push_to_history: bool,
5056 cx: &mut ModelContext<Self>,
5057 ) -> Task<Result<ProjectTransaction>> {
5058 cx.spawn(|this, mut cx| async move {
5059 let mut project_transaction = ProjectTransaction::default();
5060 for (buffer, transaction) in message.buffers.into_iter().zip(message.transactions) {
5061 let buffer = this
5062 .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
5063 .await?;
5064 let transaction = language::proto::deserialize_transaction(transaction)?;
5065 project_transaction.0.insert(buffer, transaction);
5066 }
5067
5068 for (buffer, transaction) in &project_transaction.0 {
5069 buffer
5070 .update(&mut cx, |buffer, _| {
5071 buffer.wait_for_edits(transaction.edit_ids.iter().copied())
5072 })
5073 .await;
5074
5075 if push_to_history {
5076 buffer.update(&mut cx, |buffer, _| {
5077 buffer.push_transaction(transaction.clone(), Instant::now());
5078 });
5079 }
5080 }
5081
5082 Ok(project_transaction)
5083 })
5084 }
5085
5086 fn serialize_buffer_for_peer(
5087 &mut self,
5088 buffer: &ModelHandle<Buffer>,
5089 peer_id: PeerId,
5090 cx: &AppContext,
5091 ) -> proto::Buffer {
5092 let buffer_id = buffer.read(cx).remote_id();
5093 let shared_buffers = self.shared_buffers.entry(peer_id).or_default();
5094 if shared_buffers.insert(buffer_id) {
5095 proto::Buffer {
5096 variant: Some(proto::buffer::Variant::State(buffer.read(cx).to_proto())),
5097 }
5098 } else {
5099 proto::Buffer {
5100 variant: Some(proto::buffer::Variant::Id(buffer_id)),
5101 }
5102 }
5103 }
5104
5105 fn deserialize_buffer(
5106 &mut self,
5107 buffer: proto::Buffer,
5108 cx: &mut ModelContext<Self>,
5109 ) -> Task<Result<ModelHandle<Buffer>>> {
5110 let replica_id = self.replica_id();
5111
5112 let opened_buffer_tx = self.opened_buffer.0.clone();
5113 let mut opened_buffer_rx = self.opened_buffer.1.clone();
5114 cx.spawn(|this, mut cx| async move {
5115 match buffer.variant.ok_or_else(|| anyhow!("missing buffer"))? {
5116 proto::buffer::Variant::Id(id) => {
5117 let buffer = loop {
5118 let buffer = this.read_with(&cx, |this, cx| {
5119 this.opened_buffers
5120 .get(&id)
5121 .and_then(|buffer| buffer.upgrade(cx))
5122 });
5123 if let Some(buffer) = buffer {
5124 break buffer;
5125 }
5126 opened_buffer_rx
5127 .next()
5128 .await
5129 .ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?;
5130 };
5131 Ok(buffer)
5132 }
5133 proto::buffer::Variant::State(mut buffer) => {
5134 let mut buffer_worktree = None;
5135 let mut buffer_file = None;
5136 if let Some(file) = buffer.file.take() {
5137 this.read_with(&cx, |this, cx| {
5138 let worktree_id = WorktreeId::from_proto(file.worktree_id);
5139 let worktree =
5140 this.worktree_for_id(worktree_id, cx).ok_or_else(|| {
5141 anyhow!("no worktree found for id {}", file.worktree_id)
5142 })?;
5143 buffer_file =
5144 Some(Box::new(File::from_proto(file, worktree.clone(), cx)?)
5145 as Box<dyn language::File>);
5146 buffer_worktree = Some(worktree);
5147 Ok::<_, anyhow::Error>(())
5148 })?;
5149 }
5150
5151 let buffer = cx.add_model(|cx| {
5152 Buffer::from_proto(replica_id, buffer, buffer_file, cx).unwrap()
5153 });
5154
5155 this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))?;
5156
5157 *opened_buffer_tx.borrow_mut().borrow_mut() = ();
5158 Ok(buffer)
5159 }
5160 }
5161 })
5162 }
5163
5164 fn deserialize_symbol(&self, serialized_symbol: proto::Symbol) -> Result<Symbol> {
5165 let source_worktree_id = WorktreeId::from_proto(serialized_symbol.source_worktree_id);
5166 let worktree_id = WorktreeId::from_proto(serialized_symbol.worktree_id);
5167 let start = serialized_symbol
5168 .start
5169 .ok_or_else(|| anyhow!("invalid start"))?;
5170 let end = serialized_symbol
5171 .end
5172 .ok_or_else(|| anyhow!("invalid end"))?;
5173 let kind = unsafe { mem::transmute(serialized_symbol.kind) };
5174 let path = PathBuf::from(serialized_symbol.path);
5175 let language = self.languages.select_language(&path);
5176 Ok(Symbol {
5177 source_worktree_id,
5178 worktree_id,
5179 language_server_name: LanguageServerName(serialized_symbol.language_server_name.into()),
5180 label: language
5181 .and_then(|language| language.label_for_symbol(&serialized_symbol.name, kind))
5182 .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None)),
5183 name: serialized_symbol.name,
5184 path,
5185 range: PointUtf16::new(start.row, start.column)..PointUtf16::new(end.row, end.column),
5186 kind,
5187 signature: serialized_symbol
5188 .signature
5189 .try_into()
5190 .map_err(|_| anyhow!("invalid signature"))?,
5191 })
5192 }
5193
5194 async fn handle_buffer_saved(
5195 this: ModelHandle<Self>,
5196 envelope: TypedEnvelope<proto::BufferSaved>,
5197 _: Arc<Client>,
5198 mut cx: AsyncAppContext,
5199 ) -> Result<()> {
5200 let version = deserialize_version(envelope.payload.version);
5201 let mtime = envelope
5202 .payload
5203 .mtime
5204 .ok_or_else(|| anyhow!("missing mtime"))?
5205 .into();
5206
5207 this.update(&mut cx, |this, cx| {
5208 let buffer = this
5209 .opened_buffers
5210 .get(&envelope.payload.buffer_id)
5211 .and_then(|buffer| buffer.upgrade(cx));
5212 if let Some(buffer) = buffer {
5213 buffer.update(cx, |buffer, cx| {
5214 buffer.did_save(version, mtime, None, cx);
5215 });
5216 }
5217 Ok(())
5218 })
5219 }
5220
5221 async fn handle_buffer_reloaded(
5222 this: ModelHandle<Self>,
5223 envelope: TypedEnvelope<proto::BufferReloaded>,
5224 _: Arc<Client>,
5225 mut cx: AsyncAppContext,
5226 ) -> Result<()> {
5227 let payload = envelope.payload.clone();
5228 let version = deserialize_version(payload.version);
5229 let mtime = payload
5230 .mtime
5231 .ok_or_else(|| anyhow!("missing mtime"))?
5232 .into();
5233 this.update(&mut cx, |this, cx| {
5234 let buffer = this
5235 .opened_buffers
5236 .get(&payload.buffer_id)
5237 .and_then(|buffer| buffer.upgrade(cx));
5238 if let Some(buffer) = buffer {
5239 buffer.update(cx, |buffer, cx| {
5240 buffer.did_reload(version, mtime, cx);
5241 });
5242 }
5243 Ok(())
5244 })
5245 }
5246
5247 pub fn match_paths<'a>(
5248 &self,
5249 query: &'a str,
5250 include_ignored: bool,
5251 smart_case: bool,
5252 max_results: usize,
5253 cancel_flag: &'a AtomicBool,
5254 cx: &AppContext,
5255 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
5256 let worktrees = self
5257 .worktrees(cx)
5258 .filter(|worktree| worktree.read(cx).is_visible())
5259 .collect::<Vec<_>>();
5260 let include_root_name = worktrees.len() > 1;
5261 let candidate_sets = worktrees
5262 .into_iter()
5263 .map(|worktree| CandidateSet {
5264 snapshot: worktree.read(cx).snapshot(),
5265 include_ignored,
5266 include_root_name,
5267 })
5268 .collect::<Vec<_>>();
5269
5270 let background = cx.background().clone();
5271 async move {
5272 fuzzy::match_paths(
5273 candidate_sets.as_slice(),
5274 query,
5275 smart_case,
5276 max_results,
5277 cancel_flag,
5278 background,
5279 )
5280 .await
5281 }
5282 }
5283
5284 fn edits_from_lsp(
5285 &mut self,
5286 buffer: &ModelHandle<Buffer>,
5287 lsp_edits: impl 'static + Send + IntoIterator<Item = lsp::TextEdit>,
5288 version: Option<i32>,
5289 cx: &mut ModelContext<Self>,
5290 ) -> Task<Result<Vec<(Range<Anchor>, String)>>> {
5291 let snapshot = self.buffer_snapshot_for_lsp_version(buffer, version, cx);
5292 cx.background().spawn(async move {
5293 let snapshot = snapshot?;
5294 let mut lsp_edits = lsp_edits
5295 .into_iter()
5296 .map(|edit| (range_from_lsp(edit.range), edit.new_text))
5297 .collect::<Vec<_>>();
5298 lsp_edits.sort_by_key(|(range, _)| range.start);
5299
5300 let mut lsp_edits = lsp_edits.into_iter().peekable();
5301 let mut edits = Vec::new();
5302 while let Some((mut range, mut new_text)) = lsp_edits.next() {
5303 // Combine any LSP edits that are adjacent.
5304 //
5305 // Also, combine LSP edits that are separated from each other by only
5306 // a newline. This is important because for some code actions,
5307 // Rust-analyzer rewrites the entire buffer via a series of edits that
5308 // are separated by unchanged newline characters.
5309 //
5310 // In order for the diffing logic below to work properly, any edits that
5311 // cancel each other out must be combined into one.
5312 while let Some((next_range, next_text)) = lsp_edits.peek() {
5313 if next_range.start > range.end {
5314 if next_range.start.row > range.end.row + 1
5315 || next_range.start.column > 0
5316 || snapshot.clip_point_utf16(
5317 PointUtf16::new(range.end.row, u32::MAX),
5318 Bias::Left,
5319 ) > range.end
5320 {
5321 break;
5322 }
5323 new_text.push('\n');
5324 }
5325 range.end = next_range.end;
5326 new_text.push_str(&next_text);
5327 lsp_edits.next();
5328 }
5329
5330 if snapshot.clip_point_utf16(range.start, Bias::Left) != range.start
5331 || snapshot.clip_point_utf16(range.end, Bias::Left) != range.end
5332 {
5333 return Err(anyhow!("invalid edits received from language server"));
5334 }
5335
5336 // For multiline edits, perform a diff of the old and new text so that
5337 // we can identify the changes more precisely, preserving the locations
5338 // of any anchors positioned in the unchanged regions.
5339 if range.end.row > range.start.row {
5340 let mut offset = range.start.to_offset(&snapshot);
5341 let old_text = snapshot.text_for_range(range).collect::<String>();
5342
5343 let diff = TextDiff::from_lines(old_text.as_str(), &new_text);
5344 let mut moved_since_edit = true;
5345 for change in diff.iter_all_changes() {
5346 let tag = change.tag();
5347 let value = change.value();
5348 match tag {
5349 ChangeTag::Equal => {
5350 offset += value.len();
5351 moved_since_edit = true;
5352 }
5353 ChangeTag::Delete => {
5354 let start = snapshot.anchor_after(offset);
5355 let end = snapshot.anchor_before(offset + value.len());
5356 if moved_since_edit {
5357 edits.push((start..end, String::new()));
5358 } else {
5359 edits.last_mut().unwrap().0.end = end;
5360 }
5361 offset += value.len();
5362 moved_since_edit = false;
5363 }
5364 ChangeTag::Insert => {
5365 if moved_since_edit {
5366 let anchor = snapshot.anchor_after(offset);
5367 edits.push((anchor.clone()..anchor, value.to_string()));
5368 } else {
5369 edits.last_mut().unwrap().1.push_str(value);
5370 }
5371 moved_since_edit = false;
5372 }
5373 }
5374 }
5375 } else if range.end == range.start {
5376 let anchor = snapshot.anchor_after(range.start);
5377 edits.push((anchor.clone()..anchor, new_text));
5378 } else {
5379 let edit_start = snapshot.anchor_after(range.start);
5380 let edit_end = snapshot.anchor_before(range.end);
5381 edits.push((edit_start..edit_end, new_text));
5382 }
5383 }
5384
5385 Ok(edits)
5386 })
5387 }
5388
5389 fn buffer_snapshot_for_lsp_version(
5390 &mut self,
5391 buffer: &ModelHandle<Buffer>,
5392 version: Option<i32>,
5393 cx: &AppContext,
5394 ) -> Result<TextBufferSnapshot> {
5395 const OLD_VERSIONS_TO_RETAIN: i32 = 10;
5396
5397 if let Some(version) = version {
5398 let buffer_id = buffer.read(cx).remote_id();
5399 let snapshots = self
5400 .buffer_snapshots
5401 .get_mut(&buffer_id)
5402 .ok_or_else(|| anyhow!("no snapshot found for buffer {}", buffer_id))?;
5403 let mut found_snapshot = None;
5404 snapshots.retain(|(snapshot_version, snapshot)| {
5405 if snapshot_version + OLD_VERSIONS_TO_RETAIN < version {
5406 false
5407 } else {
5408 if *snapshot_version == version {
5409 found_snapshot = Some(snapshot.clone());
5410 }
5411 true
5412 }
5413 });
5414
5415 found_snapshot.ok_or_else(|| {
5416 anyhow!(
5417 "snapshot not found for buffer {} at version {}",
5418 buffer_id,
5419 version
5420 )
5421 })
5422 } else {
5423 Ok((buffer.read(cx)).text_snapshot())
5424 }
5425 }
5426
5427 fn language_server_for_buffer(
5428 &self,
5429 buffer: &Buffer,
5430 cx: &AppContext,
5431 ) -> Option<&(Arc<dyn LspAdapter>, Arc<LanguageServer>)> {
5432 if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
5433 let worktree_id = file.worktree_id(cx);
5434 self.language_servers
5435 .get(&(worktree_id, language.lsp_adapter()?.name()))
5436 } else {
5437 None
5438 }
5439 }
5440}
5441
5442impl ProjectStore {
5443 pub fn new(db: Arc<Db>) -> Self {
5444 Self {
5445 db,
5446 projects: Default::default(),
5447 }
5448 }
5449
5450 pub fn projects<'a>(
5451 &'a self,
5452 cx: &'a AppContext,
5453 ) -> impl 'a + Iterator<Item = ModelHandle<Project>> {
5454 self.projects
5455 .iter()
5456 .filter_map(|project| project.upgrade(cx))
5457 }
5458
5459 fn add_project(&mut self, project: WeakModelHandle<Project>, cx: &mut ModelContext<Self>) {
5460 if let Err(ix) = self
5461 .projects
5462 .binary_search_by_key(&project.id(), WeakModelHandle::id)
5463 {
5464 self.projects.insert(ix, project);
5465 }
5466 cx.notify();
5467 }
5468
5469 fn prune_projects(&mut self, cx: &mut ModelContext<Self>) {
5470 let mut did_change = false;
5471 self.projects.retain(|project| {
5472 if project.is_upgradable(cx) {
5473 true
5474 } else {
5475 did_change = true;
5476 false
5477 }
5478 });
5479 if did_change {
5480 cx.notify();
5481 }
5482 }
5483}
5484
5485impl WorktreeHandle {
5486 pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
5487 match self {
5488 WorktreeHandle::Strong(handle) => Some(handle.clone()),
5489 WorktreeHandle::Weak(handle) => handle.upgrade(cx),
5490 }
5491 }
5492}
5493
5494impl OpenBuffer {
5495 pub fn upgrade(&self, cx: &impl UpgradeModelHandle) -> Option<ModelHandle<Buffer>> {
5496 match self {
5497 OpenBuffer::Strong(handle) => Some(handle.clone()),
5498 OpenBuffer::Weak(handle) => handle.upgrade(cx),
5499 OpenBuffer::Loading(_) => None,
5500 }
5501 }
5502}
5503
5504struct CandidateSet {
5505 snapshot: Snapshot,
5506 include_ignored: bool,
5507 include_root_name: bool,
5508}
5509
5510impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
5511 type Candidates = CandidateSetIter<'a>;
5512
5513 fn id(&self) -> usize {
5514 self.snapshot.id().to_usize()
5515 }
5516
5517 fn len(&self) -> usize {
5518 if self.include_ignored {
5519 self.snapshot.file_count()
5520 } else {
5521 self.snapshot.visible_file_count()
5522 }
5523 }
5524
5525 fn prefix(&self) -> Arc<str> {
5526 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
5527 self.snapshot.root_name().into()
5528 } else if self.include_root_name {
5529 format!("{}/", self.snapshot.root_name()).into()
5530 } else {
5531 "".into()
5532 }
5533 }
5534
5535 fn candidates(&'a self, start: usize) -> Self::Candidates {
5536 CandidateSetIter {
5537 traversal: self.snapshot.files(self.include_ignored, start),
5538 }
5539 }
5540}
5541
5542struct CandidateSetIter<'a> {
5543 traversal: Traversal<'a>,
5544}
5545
5546impl<'a> Iterator for CandidateSetIter<'a> {
5547 type Item = PathMatchCandidate<'a>;
5548
5549 fn next(&mut self) -> Option<Self::Item> {
5550 self.traversal.next().map(|entry| {
5551 if let EntryKind::File(char_bag) = entry.kind {
5552 PathMatchCandidate {
5553 path: &entry.path,
5554 char_bag,
5555 }
5556 } else {
5557 unreachable!()
5558 }
5559 })
5560 }
5561}
5562
5563impl Entity for ProjectStore {
5564 type Event = ();
5565}
5566
5567impl Entity for Project {
5568 type Event = Event;
5569
5570 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
5571 self.project_store.update(cx, ProjectStore::prune_projects);
5572
5573 match &self.client_state {
5574 ProjectClientState::Local { remote_id_rx, .. } => {
5575 if let Some(project_id) = *remote_id_rx.borrow() {
5576 self.client
5577 .send(proto::UnregisterProject { project_id })
5578 .log_err();
5579 }
5580 }
5581 ProjectClientState::Remote { remote_id, .. } => {
5582 self.client
5583 .send(proto::LeaveProject {
5584 project_id: *remote_id,
5585 })
5586 .log_err();
5587 }
5588 }
5589 }
5590
5591 fn app_will_quit(
5592 &mut self,
5593 _: &mut MutableAppContext,
5594 ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
5595 let shutdown_futures = self
5596 .language_servers
5597 .drain()
5598 .filter_map(|(_, (_, server))| server.shutdown())
5599 .collect::<Vec<_>>();
5600 Some(
5601 async move {
5602 futures::future::join_all(shutdown_futures).await;
5603 }
5604 .boxed(),
5605 )
5606 }
5607}
5608
5609impl Collaborator {
5610 fn from_proto(
5611 message: proto::Collaborator,
5612 user_store: &ModelHandle<UserStore>,
5613 cx: &mut AsyncAppContext,
5614 ) -> impl Future<Output = Result<Self>> {
5615 let user = user_store.update(cx, |user_store, cx| {
5616 user_store.fetch_user(message.user_id, cx)
5617 });
5618
5619 async move {
5620 Ok(Self {
5621 peer_id: PeerId(message.peer_id),
5622 user: user.await?,
5623 replica_id: message.replica_id as ReplicaId,
5624 })
5625 }
5626 }
5627}
5628
5629impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
5630 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
5631 Self {
5632 worktree_id,
5633 path: path.as_ref().into(),
5634 }
5635 }
5636}
5637
5638impl From<lsp::CreateFileOptions> for fs::CreateOptions {
5639 fn from(options: lsp::CreateFileOptions) -> Self {
5640 Self {
5641 overwrite: options.overwrite.unwrap_or(false),
5642 ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
5643 }
5644 }
5645}
5646
5647impl From<lsp::RenameFileOptions> for fs::RenameOptions {
5648 fn from(options: lsp::RenameFileOptions) -> Self {
5649 Self {
5650 overwrite: options.overwrite.unwrap_or(false),
5651 ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
5652 }
5653 }
5654}
5655
5656impl From<lsp::DeleteFileOptions> for fs::RemoveOptions {
5657 fn from(options: lsp::DeleteFileOptions) -> Self {
5658 Self {
5659 recursive: options.recursive.unwrap_or(false),
5660 ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
5661 }
5662 }
5663}
5664
5665fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
5666 proto::Symbol {
5667 source_worktree_id: symbol.source_worktree_id.to_proto(),
5668 worktree_id: symbol.worktree_id.to_proto(),
5669 language_server_name: symbol.language_server_name.0.to_string(),
5670 name: symbol.name.clone(),
5671 kind: unsafe { mem::transmute(symbol.kind) },
5672 path: symbol.path.to_string_lossy().to_string(),
5673 start: Some(proto::Point {
5674 row: symbol.range.start.row,
5675 column: symbol.range.start.column,
5676 }),
5677 end: Some(proto::Point {
5678 row: symbol.range.end.row,
5679 column: symbol.range.end.column,
5680 }),
5681 signature: symbol.signature.to_vec(),
5682 }
5683}
5684
5685fn relativize_path(base: &Path, path: &Path) -> PathBuf {
5686 let mut path_components = path.components();
5687 let mut base_components = base.components();
5688 let mut components: Vec<Component> = Vec::new();
5689 loop {
5690 match (path_components.next(), base_components.next()) {
5691 (None, None) => break,
5692 (Some(a), None) => {
5693 components.push(a);
5694 components.extend(path_components.by_ref());
5695 break;
5696 }
5697 (None, _) => components.push(Component::ParentDir),
5698 (Some(a), Some(b)) if components.is_empty() && a == b => (),
5699 (Some(a), Some(b)) if b == Component::CurDir => components.push(a),
5700 (Some(a), Some(_)) => {
5701 components.push(Component::ParentDir);
5702 for _ in base_components {
5703 components.push(Component::ParentDir);
5704 }
5705 components.push(a);
5706 components.extend(path_components.by_ref());
5707 break;
5708 }
5709 }
5710 }
5711 components.iter().map(|c| c.as_os_str()).collect()
5712}
5713
5714impl Item for Buffer {
5715 fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
5716 File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
5717 }
5718}
5719
5720#[cfg(test)]
5721mod tests {
5722 use crate::worktree::WorktreeHandle;
5723
5724 use super::{Event, *};
5725 use fs::RealFs;
5726 use futures::{future, StreamExt};
5727 use gpui::test::subscribe;
5728 use language::{
5729 tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
5730 OffsetRangeExt, Point, ToPoint,
5731 };
5732 use lsp::Url;
5733 use serde_json::json;
5734 use std::{cell::RefCell, os::unix, path::PathBuf, rc::Rc, task::Poll};
5735 use unindent::Unindent as _;
5736 use util::{assert_set_eq, test::temp_tree};
5737
5738 #[gpui::test]
5739 async fn test_populate_and_search(cx: &mut gpui::TestAppContext) {
5740 let dir = temp_tree(json!({
5741 "root": {
5742 "apple": "",
5743 "banana": {
5744 "carrot": {
5745 "date": "",
5746 "endive": "",
5747 }
5748 },
5749 "fennel": {
5750 "grape": "",
5751 }
5752 }
5753 }));
5754
5755 let root_link_path = dir.path().join("root_link");
5756 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
5757 unix::fs::symlink(
5758 &dir.path().join("root/fennel"),
5759 &dir.path().join("root/finnochio"),
5760 )
5761 .unwrap();
5762
5763 let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
5764
5765 project.read_with(cx, |project, cx| {
5766 let tree = project.worktrees(cx).next().unwrap().read(cx);
5767 assert_eq!(tree.file_count(), 5);
5768 assert_eq!(
5769 tree.inode_for_path("fennel/grape"),
5770 tree.inode_for_path("finnochio/grape")
5771 );
5772 });
5773
5774 let cancel_flag = Default::default();
5775 let results = project
5776 .read_with(cx, |project, cx| {
5777 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
5778 })
5779 .await;
5780 assert_eq!(
5781 results
5782 .into_iter()
5783 .map(|result| result.path)
5784 .collect::<Vec<Arc<Path>>>(),
5785 vec![
5786 PathBuf::from("banana/carrot/date").into(),
5787 PathBuf::from("banana/carrot/endive").into(),
5788 ]
5789 );
5790 }
5791
5792 #[gpui::test]
5793 async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
5794 cx.foreground().forbid_parking();
5795
5796 let mut rust_language = Language::new(
5797 LanguageConfig {
5798 name: "Rust".into(),
5799 path_suffixes: vec!["rs".to_string()],
5800 ..Default::default()
5801 },
5802 Some(tree_sitter_rust::language()),
5803 );
5804 let mut json_language = Language::new(
5805 LanguageConfig {
5806 name: "JSON".into(),
5807 path_suffixes: vec!["json".to_string()],
5808 ..Default::default()
5809 },
5810 None,
5811 );
5812 let mut fake_rust_servers = rust_language.set_fake_lsp_adapter(FakeLspAdapter {
5813 name: "the-rust-language-server",
5814 capabilities: lsp::ServerCapabilities {
5815 completion_provider: Some(lsp::CompletionOptions {
5816 trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
5817 ..Default::default()
5818 }),
5819 ..Default::default()
5820 },
5821 ..Default::default()
5822 });
5823 let mut fake_json_servers = json_language.set_fake_lsp_adapter(FakeLspAdapter {
5824 name: "the-json-language-server",
5825 capabilities: lsp::ServerCapabilities {
5826 completion_provider: Some(lsp::CompletionOptions {
5827 trigger_characters: Some(vec![":".to_string()]),
5828 ..Default::default()
5829 }),
5830 ..Default::default()
5831 },
5832 ..Default::default()
5833 });
5834
5835 let fs = FakeFs::new(cx.background());
5836 fs.insert_tree(
5837 "/the-root",
5838 json!({
5839 "test.rs": "const A: i32 = 1;",
5840 "test2.rs": "",
5841 "Cargo.toml": "a = 1",
5842 "package.json": "{\"a\": 1}",
5843 }),
5844 )
5845 .await;
5846
5847 let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
5848 project.update(cx, |project, _| {
5849 project.languages.add(Arc::new(rust_language));
5850 project.languages.add(Arc::new(json_language));
5851 });
5852
5853 // Open a buffer without an associated language server.
5854 let toml_buffer = project
5855 .update(cx, |project, cx| {
5856 project.open_local_buffer("/the-root/Cargo.toml", cx)
5857 })
5858 .await
5859 .unwrap();
5860
5861 // Open a buffer with an associated language server.
5862 let rust_buffer = project
5863 .update(cx, |project, cx| {
5864 project.open_local_buffer("/the-root/test.rs", cx)
5865 })
5866 .await
5867 .unwrap();
5868
5869 // A server is started up, and it is notified about Rust files.
5870 let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
5871 assert_eq!(
5872 fake_rust_server
5873 .receive_notification::<lsp::notification::DidOpenTextDocument>()
5874 .await
5875 .text_document,
5876 lsp::TextDocumentItem {
5877 uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
5878 version: 0,
5879 text: "const A: i32 = 1;".to_string(),
5880 language_id: Default::default()
5881 }
5882 );
5883
5884 // The buffer is configured based on the language server's capabilities.
5885 rust_buffer.read_with(cx, |buffer, _| {
5886 assert_eq!(
5887 buffer.completion_triggers(),
5888 &[".".to_string(), "::".to_string()]
5889 );
5890 });
5891 toml_buffer.read_with(cx, |buffer, _| {
5892 assert!(buffer.completion_triggers().is_empty());
5893 });
5894
5895 // Edit a buffer. The changes are reported to the language server.
5896 rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], cx));
5897 assert_eq!(
5898 fake_rust_server
5899 .receive_notification::<lsp::notification::DidChangeTextDocument>()
5900 .await
5901 .text_document,
5902 lsp::VersionedTextDocumentIdentifier::new(
5903 lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
5904 1
5905 )
5906 );
5907
5908 // Open a third buffer with a different associated language server.
5909 let json_buffer = project
5910 .update(cx, |project, cx| {
5911 project.open_local_buffer("/the-root/package.json", cx)
5912 })
5913 .await
5914 .unwrap();
5915
5916 // A json language server is started up and is only notified about the json buffer.
5917 let mut fake_json_server = fake_json_servers.next().await.unwrap();
5918 assert_eq!(
5919 fake_json_server
5920 .receive_notification::<lsp::notification::DidOpenTextDocument>()
5921 .await
5922 .text_document,
5923 lsp::TextDocumentItem {
5924 uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
5925 version: 0,
5926 text: "{\"a\": 1}".to_string(),
5927 language_id: Default::default()
5928 }
5929 );
5930
5931 // This buffer is configured based on the second language server's
5932 // capabilities.
5933 json_buffer.read_with(cx, |buffer, _| {
5934 assert_eq!(buffer.completion_triggers(), &[":".to_string()]);
5935 });
5936
5937 // When opening another buffer whose language server is already running,
5938 // it is also configured based on the existing language server's capabilities.
5939 let rust_buffer2 = project
5940 .update(cx, |project, cx| {
5941 project.open_local_buffer("/the-root/test2.rs", cx)
5942 })
5943 .await
5944 .unwrap();
5945 rust_buffer2.read_with(cx, |buffer, _| {
5946 assert_eq!(
5947 buffer.completion_triggers(),
5948 &[".".to_string(), "::".to_string()]
5949 );
5950 });
5951
5952 // Changes are reported only to servers matching the buffer's language.
5953 toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], cx));
5954 rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "let x = 1;")], cx));
5955 assert_eq!(
5956 fake_rust_server
5957 .receive_notification::<lsp::notification::DidChangeTextDocument>()
5958 .await
5959 .text_document,
5960 lsp::VersionedTextDocumentIdentifier::new(
5961 lsp::Url::from_file_path("/the-root/test2.rs").unwrap(),
5962 1
5963 )
5964 );
5965
5966 // Save notifications are reported to all servers.
5967 toml_buffer
5968 .update(cx, |buffer, cx| buffer.save(cx))
5969 .await
5970 .unwrap();
5971 assert_eq!(
5972 fake_rust_server
5973 .receive_notification::<lsp::notification::DidSaveTextDocument>()
5974 .await
5975 .text_document,
5976 lsp::TextDocumentIdentifier::new(
5977 lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap()
5978 )
5979 );
5980 assert_eq!(
5981 fake_json_server
5982 .receive_notification::<lsp::notification::DidSaveTextDocument>()
5983 .await
5984 .text_document,
5985 lsp::TextDocumentIdentifier::new(
5986 lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap()
5987 )
5988 );
5989
5990 // Renames are reported only to servers matching the buffer's language.
5991 fs.rename(
5992 Path::new("/the-root/test2.rs"),
5993 Path::new("/the-root/test3.rs"),
5994 Default::default(),
5995 )
5996 .await
5997 .unwrap();
5998 assert_eq!(
5999 fake_rust_server
6000 .receive_notification::<lsp::notification::DidCloseTextDocument>()
6001 .await
6002 .text_document,
6003 lsp::TextDocumentIdentifier::new(
6004 lsp::Url::from_file_path("/the-root/test2.rs").unwrap()
6005 ),
6006 );
6007 assert_eq!(
6008 fake_rust_server
6009 .receive_notification::<lsp::notification::DidOpenTextDocument>()
6010 .await
6011 .text_document,
6012 lsp::TextDocumentItem {
6013 uri: lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),
6014 version: 0,
6015 text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
6016 language_id: Default::default()
6017 },
6018 );
6019
6020 rust_buffer2.update(cx, |buffer, cx| {
6021 buffer.update_diagnostics(
6022 DiagnosticSet::from_sorted_entries(
6023 vec![DiagnosticEntry {
6024 diagnostic: Default::default(),
6025 range: Anchor::MIN..Anchor::MAX,
6026 }],
6027 &buffer.snapshot(),
6028 ),
6029 cx,
6030 );
6031 assert_eq!(
6032 buffer
6033 .snapshot()
6034 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
6035 .count(),
6036 1
6037 );
6038 });
6039
6040 // When the rename changes the extension of the file, the buffer gets closed on the old
6041 // language server and gets opened on the new one.
6042 fs.rename(
6043 Path::new("/the-root/test3.rs"),
6044 Path::new("/the-root/test3.json"),
6045 Default::default(),
6046 )
6047 .await
6048 .unwrap();
6049 assert_eq!(
6050 fake_rust_server
6051 .receive_notification::<lsp::notification::DidCloseTextDocument>()
6052 .await
6053 .text_document,
6054 lsp::TextDocumentIdentifier::new(
6055 lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),
6056 ),
6057 );
6058 assert_eq!(
6059 fake_json_server
6060 .receive_notification::<lsp::notification::DidOpenTextDocument>()
6061 .await
6062 .text_document,
6063 lsp::TextDocumentItem {
6064 uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
6065 version: 0,
6066 text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
6067 language_id: Default::default()
6068 },
6069 );
6070
6071 // We clear the diagnostics, since the language has changed.
6072 rust_buffer2.read_with(cx, |buffer, _| {
6073 assert_eq!(
6074 buffer
6075 .snapshot()
6076 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
6077 .count(),
6078 0
6079 );
6080 });
6081
6082 // The renamed file's version resets after changing language server.
6083 rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], cx));
6084 assert_eq!(
6085 fake_json_server
6086 .receive_notification::<lsp::notification::DidChangeTextDocument>()
6087 .await
6088 .text_document,
6089 lsp::VersionedTextDocumentIdentifier::new(
6090 lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
6091 1
6092 )
6093 );
6094
6095 // Restart language servers
6096 project.update(cx, |project, cx| {
6097 project.restart_language_servers_for_buffers(
6098 vec![rust_buffer.clone(), json_buffer.clone()],
6099 cx,
6100 );
6101 });
6102
6103 let mut rust_shutdown_requests = fake_rust_server
6104 .handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
6105 let mut json_shutdown_requests = fake_json_server
6106 .handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
6107 futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next());
6108
6109 let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
6110 let mut fake_json_server = fake_json_servers.next().await.unwrap();
6111
6112 // Ensure rust document is reopened in new rust language server
6113 assert_eq!(
6114 fake_rust_server
6115 .receive_notification::<lsp::notification::DidOpenTextDocument>()
6116 .await
6117 .text_document,
6118 lsp::TextDocumentItem {
6119 uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
6120 version: 1,
6121 text: rust_buffer.read_with(cx, |buffer, _| buffer.text()),
6122 language_id: Default::default()
6123 }
6124 );
6125
6126 // Ensure json documents are reopened in new json language server
6127 assert_set_eq!(
6128 [
6129 fake_json_server
6130 .receive_notification::<lsp::notification::DidOpenTextDocument>()
6131 .await
6132 .text_document,
6133 fake_json_server
6134 .receive_notification::<lsp::notification::DidOpenTextDocument>()
6135 .await
6136 .text_document,
6137 ],
6138 [
6139 lsp::TextDocumentItem {
6140 uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
6141 version: 0,
6142 text: json_buffer.read_with(cx, |buffer, _| buffer.text()),
6143 language_id: Default::default()
6144 },
6145 lsp::TextDocumentItem {
6146 uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
6147 version: 1,
6148 text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
6149 language_id: Default::default()
6150 }
6151 ]
6152 );
6153
6154 // Close notifications are reported only to servers matching the buffer's language.
6155 cx.update(|_| drop(json_buffer));
6156 let close_message = lsp::DidCloseTextDocumentParams {
6157 text_document: lsp::TextDocumentIdentifier::new(
6158 lsp::Url::from_file_path("/the-root/package.json").unwrap(),
6159 ),
6160 };
6161 assert_eq!(
6162 fake_json_server
6163 .receive_notification::<lsp::notification::DidCloseTextDocument>()
6164 .await,
6165 close_message,
6166 );
6167 }
6168
6169 #[gpui::test]
6170 async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
6171 cx.foreground().forbid_parking();
6172
6173 let fs = FakeFs::new(cx.background());
6174 fs.insert_tree(
6175 "/dir",
6176 json!({
6177 "a.rs": "let a = 1;",
6178 "b.rs": "let b = 2;"
6179 }),
6180 )
6181 .await;
6182
6183 let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await;
6184
6185 let buffer_a = project
6186 .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
6187 .await
6188 .unwrap();
6189 let buffer_b = project
6190 .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
6191 .await
6192 .unwrap();
6193
6194 project.update(cx, |project, cx| {
6195 project
6196 .update_diagnostics(
6197 0,
6198 lsp::PublishDiagnosticsParams {
6199 uri: Url::from_file_path("/dir/a.rs").unwrap(),
6200 version: None,
6201 diagnostics: vec![lsp::Diagnostic {
6202 range: lsp::Range::new(
6203 lsp::Position::new(0, 4),
6204 lsp::Position::new(0, 5),
6205 ),
6206 severity: Some(lsp::DiagnosticSeverity::ERROR),
6207 message: "error 1".to_string(),
6208 ..Default::default()
6209 }],
6210 },
6211 &[],
6212 cx,
6213 )
6214 .unwrap();
6215 project
6216 .update_diagnostics(
6217 0,
6218 lsp::PublishDiagnosticsParams {
6219 uri: Url::from_file_path("/dir/b.rs").unwrap(),
6220 version: None,
6221 diagnostics: vec![lsp::Diagnostic {
6222 range: lsp::Range::new(
6223 lsp::Position::new(0, 4),
6224 lsp::Position::new(0, 5),
6225 ),
6226 severity: Some(lsp::DiagnosticSeverity::WARNING),
6227 message: "error 2".to_string(),
6228 ..Default::default()
6229 }],
6230 },
6231 &[],
6232 cx,
6233 )
6234 .unwrap();
6235 });
6236
6237 buffer_a.read_with(cx, |buffer, _| {
6238 let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len());
6239 assert_eq!(
6240 chunks
6241 .iter()
6242 .map(|(s, d)| (s.as_str(), *d))
6243 .collect::<Vec<_>>(),
6244 &[
6245 ("let ", None),
6246 ("a", Some(DiagnosticSeverity::ERROR)),
6247 (" = 1;", None),
6248 ]
6249 );
6250 });
6251 buffer_b.read_with(cx, |buffer, _| {
6252 let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len());
6253 assert_eq!(
6254 chunks
6255 .iter()
6256 .map(|(s, d)| (s.as_str(), *d))
6257 .collect::<Vec<_>>(),
6258 &[
6259 ("let ", None),
6260 ("b", Some(DiagnosticSeverity::WARNING)),
6261 (" = 2;", None),
6262 ]
6263 );
6264 });
6265 }
6266
6267 #[gpui::test]
6268 async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
6269 cx.foreground().forbid_parking();
6270
6271 let progress_token = "the-progress-token";
6272 let mut language = Language::new(
6273 LanguageConfig {
6274 name: "Rust".into(),
6275 path_suffixes: vec!["rs".to_string()],
6276 ..Default::default()
6277 },
6278 Some(tree_sitter_rust::language()),
6279 );
6280 let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
6281 disk_based_diagnostics_progress_token: Some(progress_token),
6282 disk_based_diagnostics_sources: &["disk"],
6283 ..Default::default()
6284 });
6285
6286 let fs = FakeFs::new(cx.background());
6287 fs.insert_tree(
6288 "/dir",
6289 json!({
6290 "a.rs": "fn a() { A }",
6291 "b.rs": "const y: i32 = 1",
6292 }),
6293 )
6294 .await;
6295
6296 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
6297 project.update(cx, |project, _| project.languages.add(Arc::new(language)));
6298 let worktree_id =
6299 project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
6300
6301 // Cause worktree to start the fake language server
6302 let _buffer = project
6303 .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
6304 .await
6305 .unwrap();
6306
6307 let mut events = subscribe(&project, cx);
6308
6309 let mut fake_server = fake_servers.next().await.unwrap();
6310 fake_server.start_progress(progress_token).await;
6311 assert_eq!(
6312 events.next().await.unwrap(),
6313 Event::DiskBasedDiagnosticsStarted {
6314 language_server_id: 0,
6315 }
6316 );
6317
6318 fake_server.start_progress(progress_token).await;
6319 fake_server.end_progress(progress_token).await;
6320 fake_server.start_progress(progress_token).await;
6321
6322 fake_server.notify::<lsp::notification::PublishDiagnostics>(
6323 lsp::PublishDiagnosticsParams {
6324 uri: Url::from_file_path("/dir/a.rs").unwrap(),
6325 version: None,
6326 diagnostics: vec![lsp::Diagnostic {
6327 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
6328 severity: Some(lsp::DiagnosticSeverity::ERROR),
6329 message: "undefined variable 'A'".to_string(),
6330 ..Default::default()
6331 }],
6332 },
6333 );
6334 assert_eq!(
6335 events.next().await.unwrap(),
6336 Event::DiagnosticsUpdated {
6337 language_server_id: 0,
6338 path: (worktree_id, Path::new("a.rs")).into()
6339 }
6340 );
6341
6342 fake_server.end_progress(progress_token).await;
6343 fake_server.end_progress(progress_token).await;
6344 assert_eq!(
6345 events.next().await.unwrap(),
6346 Event::DiskBasedDiagnosticsFinished {
6347 language_server_id: 0
6348 }
6349 );
6350
6351 let buffer = project
6352 .update(cx, |p, cx| p.open_local_buffer("/dir/a.rs", cx))
6353 .await
6354 .unwrap();
6355
6356 buffer.read_with(cx, |buffer, _| {
6357 let snapshot = buffer.snapshot();
6358 let diagnostics = snapshot
6359 .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
6360 .collect::<Vec<_>>();
6361 assert_eq!(
6362 diagnostics,
6363 &[DiagnosticEntry {
6364 range: Point::new(0, 9)..Point::new(0, 10),
6365 diagnostic: Diagnostic {
6366 severity: lsp::DiagnosticSeverity::ERROR,
6367 message: "undefined variable 'A'".to_string(),
6368 group_id: 0,
6369 is_primary: true,
6370 ..Default::default()
6371 }
6372 }]
6373 )
6374 });
6375
6376 // Ensure publishing empty diagnostics twice only results in one update event.
6377 fake_server.notify::<lsp::notification::PublishDiagnostics>(
6378 lsp::PublishDiagnosticsParams {
6379 uri: Url::from_file_path("/dir/a.rs").unwrap(),
6380 version: None,
6381 diagnostics: Default::default(),
6382 },
6383 );
6384 assert_eq!(
6385 events.next().await.unwrap(),
6386 Event::DiagnosticsUpdated {
6387 language_server_id: 0,
6388 path: (worktree_id, Path::new("a.rs")).into()
6389 }
6390 );
6391
6392 fake_server.notify::<lsp::notification::PublishDiagnostics>(
6393 lsp::PublishDiagnosticsParams {
6394 uri: Url::from_file_path("/dir/a.rs").unwrap(),
6395 version: None,
6396 diagnostics: Default::default(),
6397 },
6398 );
6399 cx.foreground().run_until_parked();
6400 assert_eq!(futures::poll!(events.next()), Poll::Pending);
6401 }
6402
6403 #[gpui::test]
6404 async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) {
6405 cx.foreground().forbid_parking();
6406
6407 let progress_token = "the-progress-token";
6408 let mut language = Language::new(
6409 LanguageConfig {
6410 path_suffixes: vec!["rs".to_string()],
6411 ..Default::default()
6412 },
6413 None,
6414 );
6415 let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
6416 disk_based_diagnostics_sources: &["disk"],
6417 disk_based_diagnostics_progress_token: Some(progress_token),
6418 ..Default::default()
6419 });
6420
6421 let fs = FakeFs::new(cx.background());
6422 fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
6423
6424 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
6425 project.update(cx, |project, _| project.languages.add(Arc::new(language)));
6426
6427 let buffer = project
6428 .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
6429 .await
6430 .unwrap();
6431
6432 // Simulate diagnostics starting to update.
6433 let mut fake_server = fake_servers.next().await.unwrap();
6434 fake_server.start_progress(progress_token).await;
6435
6436 // Restart the server before the diagnostics finish updating.
6437 project.update(cx, |project, cx| {
6438 project.restart_language_servers_for_buffers([buffer], cx);
6439 });
6440 let mut events = subscribe(&project, cx);
6441
6442 // Simulate the newly started server sending more diagnostics.
6443 let mut fake_server = fake_servers.next().await.unwrap();
6444 fake_server.start_progress(progress_token).await;
6445 assert_eq!(
6446 events.next().await.unwrap(),
6447 Event::DiskBasedDiagnosticsStarted {
6448 language_server_id: 1
6449 }
6450 );
6451 project.read_with(cx, |project, _| {
6452 assert_eq!(
6453 project
6454 .language_servers_running_disk_based_diagnostics()
6455 .collect::<Vec<_>>(),
6456 [1]
6457 );
6458 });
6459
6460 // All diagnostics are considered done, despite the old server's diagnostic
6461 // task never completing.
6462 fake_server.end_progress(progress_token).await;
6463 assert_eq!(
6464 events.next().await.unwrap(),
6465 Event::DiskBasedDiagnosticsFinished {
6466 language_server_id: 1
6467 }
6468 );
6469 project.read_with(cx, |project, _| {
6470 assert_eq!(
6471 project
6472 .language_servers_running_disk_based_diagnostics()
6473 .collect::<Vec<_>>(),
6474 [0; 0]
6475 );
6476 });
6477 }
6478
6479 #[gpui::test]
6480 async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
6481 cx.foreground().forbid_parking();
6482
6483 let mut language = Language::new(
6484 LanguageConfig {
6485 name: "Rust".into(),
6486 path_suffixes: vec!["rs".to_string()],
6487 ..Default::default()
6488 },
6489 Some(tree_sitter_rust::language()),
6490 );
6491 let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
6492 disk_based_diagnostics_sources: &["disk"],
6493 ..Default::default()
6494 });
6495
6496 let text = "
6497 fn a() { A }
6498 fn b() { BB }
6499 fn c() { CCC }
6500 "
6501 .unindent();
6502
6503 let fs = FakeFs::new(cx.background());
6504 fs.insert_tree("/dir", json!({ "a.rs": text })).await;
6505
6506 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
6507 project.update(cx, |project, _| project.languages.add(Arc::new(language)));
6508
6509 let buffer = project
6510 .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
6511 .await
6512 .unwrap();
6513
6514 let mut fake_server = fake_servers.next().await.unwrap();
6515 let open_notification = fake_server
6516 .receive_notification::<lsp::notification::DidOpenTextDocument>()
6517 .await;
6518
6519 // Edit the buffer, moving the content down
6520 buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], cx));
6521 let change_notification_1 = fake_server
6522 .receive_notification::<lsp::notification::DidChangeTextDocument>()
6523 .await;
6524 assert!(
6525 change_notification_1.text_document.version > open_notification.text_document.version
6526 );
6527
6528 // Report some diagnostics for the initial version of the buffer
6529 fake_server.notify::<lsp::notification::PublishDiagnostics>(
6530 lsp::PublishDiagnosticsParams {
6531 uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
6532 version: Some(open_notification.text_document.version),
6533 diagnostics: vec![
6534 lsp::Diagnostic {
6535 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
6536 severity: Some(DiagnosticSeverity::ERROR),
6537 message: "undefined variable 'A'".to_string(),
6538 source: Some("disk".to_string()),
6539 ..Default::default()
6540 },
6541 lsp::Diagnostic {
6542 range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
6543 severity: Some(DiagnosticSeverity::ERROR),
6544 message: "undefined variable 'BB'".to_string(),
6545 source: Some("disk".to_string()),
6546 ..Default::default()
6547 },
6548 lsp::Diagnostic {
6549 range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)),
6550 severity: Some(DiagnosticSeverity::ERROR),
6551 source: Some("disk".to_string()),
6552 message: "undefined variable 'CCC'".to_string(),
6553 ..Default::default()
6554 },
6555 ],
6556 },
6557 );
6558
6559 // The diagnostics have moved down since they were created.
6560 buffer.next_notification(cx).await;
6561 buffer.read_with(cx, |buffer, _| {
6562 assert_eq!(
6563 buffer
6564 .snapshot()
6565 .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0), false)
6566 .collect::<Vec<_>>(),
6567 &[
6568 DiagnosticEntry {
6569 range: Point::new(3, 9)..Point::new(3, 11),
6570 diagnostic: Diagnostic {
6571 severity: DiagnosticSeverity::ERROR,
6572 message: "undefined variable 'BB'".to_string(),
6573 is_disk_based: true,
6574 group_id: 1,
6575 is_primary: true,
6576 ..Default::default()
6577 },
6578 },
6579 DiagnosticEntry {
6580 range: Point::new(4, 9)..Point::new(4, 12),
6581 diagnostic: Diagnostic {
6582 severity: DiagnosticSeverity::ERROR,
6583 message: "undefined variable 'CCC'".to_string(),
6584 is_disk_based: true,
6585 group_id: 2,
6586 is_primary: true,
6587 ..Default::default()
6588 }
6589 }
6590 ]
6591 );
6592 assert_eq!(
6593 chunks_with_diagnostics(buffer, 0..buffer.len()),
6594 [
6595 ("\n\nfn a() { ".to_string(), None),
6596 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
6597 (" }\nfn b() { ".to_string(), None),
6598 ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
6599 (" }\nfn c() { ".to_string(), None),
6600 ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
6601 (" }\n".to_string(), None),
6602 ]
6603 );
6604 assert_eq!(
6605 chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
6606 [
6607 ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
6608 (" }\nfn c() { ".to_string(), None),
6609 ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
6610 ]
6611 );
6612 });
6613
6614 // Ensure overlapping diagnostics are highlighted correctly.
6615 fake_server.notify::<lsp::notification::PublishDiagnostics>(
6616 lsp::PublishDiagnosticsParams {
6617 uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
6618 version: Some(open_notification.text_document.version),
6619 diagnostics: vec![
6620 lsp::Diagnostic {
6621 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
6622 severity: Some(DiagnosticSeverity::ERROR),
6623 message: "undefined variable 'A'".to_string(),
6624 source: Some("disk".to_string()),
6625 ..Default::default()
6626 },
6627 lsp::Diagnostic {
6628 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)),
6629 severity: Some(DiagnosticSeverity::WARNING),
6630 message: "unreachable statement".to_string(),
6631 source: Some("disk".to_string()),
6632 ..Default::default()
6633 },
6634 ],
6635 },
6636 );
6637
6638 buffer.next_notification(cx).await;
6639 buffer.read_with(cx, |buffer, _| {
6640 assert_eq!(
6641 buffer
6642 .snapshot()
6643 .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0), false)
6644 .collect::<Vec<_>>(),
6645 &[
6646 DiagnosticEntry {
6647 range: Point::new(2, 9)..Point::new(2, 12),
6648 diagnostic: Diagnostic {
6649 severity: DiagnosticSeverity::WARNING,
6650 message: "unreachable statement".to_string(),
6651 is_disk_based: true,
6652 group_id: 4,
6653 is_primary: true,
6654 ..Default::default()
6655 }
6656 },
6657 DiagnosticEntry {
6658 range: Point::new(2, 9)..Point::new(2, 10),
6659 diagnostic: Diagnostic {
6660 severity: DiagnosticSeverity::ERROR,
6661 message: "undefined variable 'A'".to_string(),
6662 is_disk_based: true,
6663 group_id: 3,
6664 is_primary: true,
6665 ..Default::default()
6666 },
6667 }
6668 ]
6669 );
6670 assert_eq!(
6671 chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
6672 [
6673 ("fn a() { ".to_string(), None),
6674 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
6675 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
6676 ("\n".to_string(), None),
6677 ]
6678 );
6679 assert_eq!(
6680 chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
6681 [
6682 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
6683 ("\n".to_string(), None),
6684 ]
6685 );
6686 });
6687
6688 // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
6689 // changes since the last save.
6690 buffer.update(cx, |buffer, cx| {
6691 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], cx);
6692 buffer.edit([(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")], cx);
6693 buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], cx);
6694 });
6695 let change_notification_2 = fake_server
6696 .receive_notification::<lsp::notification::DidChangeTextDocument>()
6697 .await;
6698 assert!(
6699 change_notification_2.text_document.version
6700 > change_notification_1.text_document.version
6701 );
6702
6703 // Handle out-of-order diagnostics
6704 fake_server.notify::<lsp::notification::PublishDiagnostics>(
6705 lsp::PublishDiagnosticsParams {
6706 uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
6707 version: Some(change_notification_2.text_document.version),
6708 diagnostics: vec![
6709 lsp::Diagnostic {
6710 range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
6711 severity: Some(DiagnosticSeverity::ERROR),
6712 message: "undefined variable 'BB'".to_string(),
6713 source: Some("disk".to_string()),
6714 ..Default::default()
6715 },
6716 lsp::Diagnostic {
6717 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
6718 severity: Some(DiagnosticSeverity::WARNING),
6719 message: "undefined variable 'A'".to_string(),
6720 source: Some("disk".to_string()),
6721 ..Default::default()
6722 },
6723 ],
6724 },
6725 );
6726
6727 buffer.next_notification(cx).await;
6728 buffer.read_with(cx, |buffer, _| {
6729 assert_eq!(
6730 buffer
6731 .snapshot()
6732 .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
6733 .collect::<Vec<_>>(),
6734 &[
6735 DiagnosticEntry {
6736 range: Point::new(2, 21)..Point::new(2, 22),
6737 diagnostic: Diagnostic {
6738 severity: DiagnosticSeverity::WARNING,
6739 message: "undefined variable 'A'".to_string(),
6740 is_disk_based: true,
6741 group_id: 6,
6742 is_primary: true,
6743 ..Default::default()
6744 }
6745 },
6746 DiagnosticEntry {
6747 range: Point::new(3, 9)..Point::new(3, 14),
6748 diagnostic: Diagnostic {
6749 severity: DiagnosticSeverity::ERROR,
6750 message: "undefined variable 'BB'".to_string(),
6751 is_disk_based: true,
6752 group_id: 5,
6753 is_primary: true,
6754 ..Default::default()
6755 },
6756 }
6757 ]
6758 );
6759 });
6760 }
6761
6762 #[gpui::test]
6763 async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
6764 cx.foreground().forbid_parking();
6765
6766 let text = concat!(
6767 "let one = ;\n", //
6768 "let two = \n",
6769 "let three = 3;\n",
6770 );
6771
6772 let fs = FakeFs::new(cx.background());
6773 fs.insert_tree("/dir", json!({ "a.rs": text })).await;
6774
6775 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
6776 let buffer = project
6777 .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
6778 .await
6779 .unwrap();
6780
6781 project.update(cx, |project, cx| {
6782 project
6783 .update_buffer_diagnostics(
6784 &buffer,
6785 vec![
6786 DiagnosticEntry {
6787 range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10),
6788 diagnostic: Diagnostic {
6789 severity: DiagnosticSeverity::ERROR,
6790 message: "syntax error 1".to_string(),
6791 ..Default::default()
6792 },
6793 },
6794 DiagnosticEntry {
6795 range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10),
6796 diagnostic: Diagnostic {
6797 severity: DiagnosticSeverity::ERROR,
6798 message: "syntax error 2".to_string(),
6799 ..Default::default()
6800 },
6801 },
6802 ],
6803 None,
6804 cx,
6805 )
6806 .unwrap();
6807 });
6808
6809 // An empty range is extended forward to include the following character.
6810 // At the end of a line, an empty range is extended backward to include
6811 // the preceding character.
6812 buffer.read_with(cx, |buffer, _| {
6813 let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len());
6814 assert_eq!(
6815 chunks
6816 .iter()
6817 .map(|(s, d)| (s.as_str(), *d))
6818 .collect::<Vec<_>>(),
6819 &[
6820 ("let one = ", None),
6821 (";", Some(DiagnosticSeverity::ERROR)),
6822 ("\nlet two =", None),
6823 (" ", Some(DiagnosticSeverity::ERROR)),
6824 ("\nlet three = 3;\n", None)
6825 ]
6826 );
6827 });
6828 }
6829
6830 #[gpui::test]
6831 async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
6832 cx.foreground().forbid_parking();
6833
6834 let mut language = Language::new(
6835 LanguageConfig {
6836 name: "Rust".into(),
6837 path_suffixes: vec!["rs".to_string()],
6838 ..Default::default()
6839 },
6840 Some(tree_sitter_rust::language()),
6841 );
6842 let mut fake_servers = language.set_fake_lsp_adapter(Default::default());
6843
6844 let text = "
6845 fn a() {
6846 f1();
6847 }
6848 fn b() {
6849 f2();
6850 }
6851 fn c() {
6852 f3();
6853 }
6854 "
6855 .unindent();
6856
6857 let fs = FakeFs::new(cx.background());
6858 fs.insert_tree(
6859 "/dir",
6860 json!({
6861 "a.rs": text.clone(),
6862 }),
6863 )
6864 .await;
6865
6866 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
6867 project.update(cx, |project, _| project.languages.add(Arc::new(language)));
6868 let buffer = project
6869 .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
6870 .await
6871 .unwrap();
6872
6873 let mut fake_server = fake_servers.next().await.unwrap();
6874 let lsp_document_version = fake_server
6875 .receive_notification::<lsp::notification::DidOpenTextDocument>()
6876 .await
6877 .text_document
6878 .version;
6879
6880 // Simulate editing the buffer after the language server computes some edits.
6881 buffer.update(cx, |buffer, cx| {
6882 buffer.edit(
6883 [(
6884 Point::new(0, 0)..Point::new(0, 0),
6885 "// above first function\n",
6886 )],
6887 cx,
6888 );
6889 buffer.edit(
6890 [(
6891 Point::new(2, 0)..Point::new(2, 0),
6892 " // inside first function\n",
6893 )],
6894 cx,
6895 );
6896 buffer.edit(
6897 [(
6898 Point::new(6, 4)..Point::new(6, 4),
6899 "// inside second function ",
6900 )],
6901 cx,
6902 );
6903
6904 assert_eq!(
6905 buffer.text(),
6906 "
6907 // above first function
6908 fn a() {
6909 // inside first function
6910 f1();
6911 }
6912 fn b() {
6913 // inside second function f2();
6914 }
6915 fn c() {
6916 f3();
6917 }
6918 "
6919 .unindent()
6920 );
6921 });
6922
6923 let edits = project
6924 .update(cx, |project, cx| {
6925 project.edits_from_lsp(
6926 &buffer,
6927 vec![
6928 // replace body of first function
6929 lsp::TextEdit {
6930 range: lsp::Range::new(
6931 lsp::Position::new(0, 0),
6932 lsp::Position::new(3, 0),
6933 ),
6934 new_text: "
6935 fn a() {
6936 f10();
6937 }
6938 "
6939 .unindent(),
6940 },
6941 // edit inside second function
6942 lsp::TextEdit {
6943 range: lsp::Range::new(
6944 lsp::Position::new(4, 6),
6945 lsp::Position::new(4, 6),
6946 ),
6947 new_text: "00".into(),
6948 },
6949 // edit inside third function via two distinct edits
6950 lsp::TextEdit {
6951 range: lsp::Range::new(
6952 lsp::Position::new(7, 5),
6953 lsp::Position::new(7, 5),
6954 ),
6955 new_text: "4000".into(),
6956 },
6957 lsp::TextEdit {
6958 range: lsp::Range::new(
6959 lsp::Position::new(7, 5),
6960 lsp::Position::new(7, 6),
6961 ),
6962 new_text: "".into(),
6963 },
6964 ],
6965 Some(lsp_document_version),
6966 cx,
6967 )
6968 })
6969 .await
6970 .unwrap();
6971
6972 buffer.update(cx, |buffer, cx| {
6973 for (range, new_text) in edits {
6974 buffer.edit([(range, new_text)], cx);
6975 }
6976 assert_eq!(
6977 buffer.text(),
6978 "
6979 // above first function
6980 fn a() {
6981 // inside first function
6982 f10();
6983 }
6984 fn b() {
6985 // inside second function f200();
6986 }
6987 fn c() {
6988 f4000();
6989 }
6990 "
6991 .unindent()
6992 );
6993 });
6994 }
6995
6996 #[gpui::test]
6997 async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
6998 cx.foreground().forbid_parking();
6999
7000 let text = "
7001 use a::b;
7002 use a::c;
7003
7004 fn f() {
7005 b();
7006 c();
7007 }
7008 "
7009 .unindent();
7010
7011 let fs = FakeFs::new(cx.background());
7012 fs.insert_tree(
7013 "/dir",
7014 json!({
7015 "a.rs": text.clone(),
7016 }),
7017 )
7018 .await;
7019
7020 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
7021 let buffer = project
7022 .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
7023 .await
7024 .unwrap();
7025
7026 // Simulate the language server sending us a small edit in the form of a very large diff.
7027 // Rust-analyzer does this when performing a merge-imports code action.
7028 let edits = project
7029 .update(cx, |project, cx| {
7030 project.edits_from_lsp(
7031 &buffer,
7032 [
7033 // Replace the first use statement without editing the semicolon.
7034 lsp::TextEdit {
7035 range: lsp::Range::new(
7036 lsp::Position::new(0, 4),
7037 lsp::Position::new(0, 8),
7038 ),
7039 new_text: "a::{b, c}".into(),
7040 },
7041 // Reinsert the remainder of the file between the semicolon and the final
7042 // newline of the file.
7043 lsp::TextEdit {
7044 range: lsp::Range::new(
7045 lsp::Position::new(0, 9),
7046 lsp::Position::new(0, 9),
7047 ),
7048 new_text: "\n\n".into(),
7049 },
7050 lsp::TextEdit {
7051 range: lsp::Range::new(
7052 lsp::Position::new(0, 9),
7053 lsp::Position::new(0, 9),
7054 ),
7055 new_text: "
7056 fn f() {
7057 b();
7058 c();
7059 }"
7060 .unindent(),
7061 },
7062 // Delete everything after the first newline of the file.
7063 lsp::TextEdit {
7064 range: lsp::Range::new(
7065 lsp::Position::new(1, 0),
7066 lsp::Position::new(7, 0),
7067 ),
7068 new_text: "".into(),
7069 },
7070 ],
7071 None,
7072 cx,
7073 )
7074 })
7075 .await
7076 .unwrap();
7077
7078 buffer.update(cx, |buffer, cx| {
7079 let edits = edits
7080 .into_iter()
7081 .map(|(range, text)| {
7082 (
7083 range.start.to_point(&buffer)..range.end.to_point(&buffer),
7084 text,
7085 )
7086 })
7087 .collect::<Vec<_>>();
7088
7089 assert_eq!(
7090 edits,
7091 [
7092 (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
7093 (Point::new(1, 0)..Point::new(2, 0), "".into())
7094 ]
7095 );
7096
7097 for (range, new_text) in edits {
7098 buffer.edit([(range, new_text)], cx);
7099 }
7100 assert_eq!(
7101 buffer.text(),
7102 "
7103 use a::{b, c};
7104
7105 fn f() {
7106 b();
7107 c();
7108 }
7109 "
7110 .unindent()
7111 );
7112 });
7113 }
7114
7115 #[gpui::test]
7116 async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
7117 cx.foreground().forbid_parking();
7118
7119 let text = "
7120 use a::b;
7121 use a::c;
7122
7123 fn f() {
7124 b();
7125 c();
7126 }
7127 "
7128 .unindent();
7129
7130 let fs = FakeFs::new(cx.background());
7131 fs.insert_tree(
7132 "/dir",
7133 json!({
7134 "a.rs": text.clone(),
7135 }),
7136 )
7137 .await;
7138
7139 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
7140 let buffer = project
7141 .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
7142 .await
7143 .unwrap();
7144
7145 // Simulate the language server sending us edits in a non-ordered fashion,
7146 // with ranges sometimes being inverted.
7147 let edits = project
7148 .update(cx, |project, cx| {
7149 project.edits_from_lsp(
7150 &buffer,
7151 [
7152 lsp::TextEdit {
7153 range: lsp::Range::new(
7154 lsp::Position::new(0, 9),
7155 lsp::Position::new(0, 9),
7156 ),
7157 new_text: "\n\n".into(),
7158 },
7159 lsp::TextEdit {
7160 range: lsp::Range::new(
7161 lsp::Position::new(0, 8),
7162 lsp::Position::new(0, 4),
7163 ),
7164 new_text: "a::{b, c}".into(),
7165 },
7166 lsp::TextEdit {
7167 range: lsp::Range::new(
7168 lsp::Position::new(1, 0),
7169 lsp::Position::new(7, 0),
7170 ),
7171 new_text: "".into(),
7172 },
7173 lsp::TextEdit {
7174 range: lsp::Range::new(
7175 lsp::Position::new(0, 9),
7176 lsp::Position::new(0, 9),
7177 ),
7178 new_text: "
7179 fn f() {
7180 b();
7181 c();
7182 }"
7183 .unindent(),
7184 },
7185 ],
7186 None,
7187 cx,
7188 )
7189 })
7190 .await
7191 .unwrap();
7192
7193 buffer.update(cx, |buffer, cx| {
7194 let edits = edits
7195 .into_iter()
7196 .map(|(range, text)| {
7197 (
7198 range.start.to_point(&buffer)..range.end.to_point(&buffer),
7199 text,
7200 )
7201 })
7202 .collect::<Vec<_>>();
7203
7204 assert_eq!(
7205 edits,
7206 [
7207 (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
7208 (Point::new(1, 0)..Point::new(2, 0), "".into())
7209 ]
7210 );
7211
7212 for (range, new_text) in edits {
7213 buffer.edit([(range, new_text)], cx);
7214 }
7215 assert_eq!(
7216 buffer.text(),
7217 "
7218 use a::{b, c};
7219
7220 fn f() {
7221 b();
7222 c();
7223 }
7224 "
7225 .unindent()
7226 );
7227 });
7228 }
7229
7230 fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
7231 buffer: &Buffer,
7232 range: Range<T>,
7233 ) -> Vec<(String, Option<DiagnosticSeverity>)> {
7234 let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
7235 for chunk in buffer.snapshot().chunks(range, true) {
7236 if chunks.last().map_or(false, |prev_chunk| {
7237 prev_chunk.1 == chunk.diagnostic_severity
7238 }) {
7239 chunks.last_mut().unwrap().0.push_str(chunk.text);
7240 } else {
7241 chunks.push((chunk.text.to_string(), chunk.diagnostic_severity));
7242 }
7243 }
7244 chunks
7245 }
7246
7247 #[gpui::test]
7248 async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) {
7249 let dir = temp_tree(json!({
7250 "root": {
7251 "dir1": {},
7252 "dir2": {
7253 "dir3": {}
7254 }
7255 }
7256 }));
7257
7258 let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await;
7259 let cancel_flag = Default::default();
7260 let results = project
7261 .read_with(cx, |project, cx| {
7262 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
7263 })
7264 .await;
7265
7266 assert!(results.is_empty());
7267 }
7268
7269 #[gpui::test(iterations = 10)]
7270 async fn test_definition(cx: &mut gpui::TestAppContext) {
7271 let mut language = Language::new(
7272 LanguageConfig {
7273 name: "Rust".into(),
7274 path_suffixes: vec!["rs".to_string()],
7275 ..Default::default()
7276 },
7277 Some(tree_sitter_rust::language()),
7278 );
7279 let mut fake_servers = language.set_fake_lsp_adapter(Default::default());
7280
7281 let fs = FakeFs::new(cx.background());
7282 fs.insert_tree(
7283 "/dir",
7284 json!({
7285 "a.rs": "const fn a() { A }",
7286 "b.rs": "const y: i32 = crate::a()",
7287 }),
7288 )
7289 .await;
7290
7291 let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await;
7292 project.update(cx, |project, _| project.languages.add(Arc::new(language)));
7293
7294 let buffer = project
7295 .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
7296 .await
7297 .unwrap();
7298
7299 let fake_server = fake_servers.next().await.unwrap();
7300 fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
7301 let params = params.text_document_position_params;
7302 assert_eq!(
7303 params.text_document.uri.to_file_path().unwrap(),
7304 Path::new("/dir/b.rs"),
7305 );
7306 assert_eq!(params.position, lsp::Position::new(0, 22));
7307
7308 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
7309 lsp::Location::new(
7310 lsp::Url::from_file_path("/dir/a.rs").unwrap(),
7311 lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
7312 ),
7313 )))
7314 });
7315
7316 let mut definitions = project
7317 .update(cx, |project, cx| project.definition(&buffer, 22, cx))
7318 .await
7319 .unwrap();
7320
7321 assert_eq!(definitions.len(), 1);
7322 let definition = definitions.pop().unwrap();
7323 cx.update(|cx| {
7324 let target_buffer = definition.buffer.read(cx);
7325 assert_eq!(
7326 target_buffer
7327 .file()
7328 .unwrap()
7329 .as_local()
7330 .unwrap()
7331 .abs_path(cx),
7332 Path::new("/dir/a.rs"),
7333 );
7334 assert_eq!(definition.range.to_offset(target_buffer), 9..10);
7335 assert_eq!(
7336 list_worktrees(&project, cx),
7337 [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)]
7338 );
7339
7340 drop(definition);
7341 });
7342 cx.read(|cx| {
7343 assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]);
7344 });
7345
7346 fn list_worktrees<'a>(
7347 project: &'a ModelHandle<Project>,
7348 cx: &'a AppContext,
7349 ) -> Vec<(&'a Path, bool)> {
7350 project
7351 .read(cx)
7352 .worktrees(cx)
7353 .map(|worktree| {
7354 let worktree = worktree.read(cx);
7355 (
7356 worktree.as_local().unwrap().abs_path().as_ref(),
7357 worktree.is_visible(),
7358 )
7359 })
7360 .collect::<Vec<_>>()
7361 }
7362 }
7363
7364 #[gpui::test]
7365 async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
7366 let mut language = Language::new(
7367 LanguageConfig {
7368 name: "TypeScript".into(),
7369 path_suffixes: vec!["ts".to_string()],
7370 ..Default::default()
7371 },
7372 Some(tree_sitter_typescript::language_typescript()),
7373 );
7374 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
7375
7376 let fs = FakeFs::new(cx.background());
7377 fs.insert_tree(
7378 "/dir",
7379 json!({
7380 "a.ts": "",
7381 }),
7382 )
7383 .await;
7384
7385 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
7386 project.update(cx, |project, _| project.languages.add(Arc::new(language)));
7387 let buffer = project
7388 .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
7389 .await
7390 .unwrap();
7391
7392 let fake_server = fake_language_servers.next().await.unwrap();
7393
7394 let text = "let a = b.fqn";
7395 buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
7396 let completions = project.update(cx, |project, cx| {
7397 project.completions(&buffer, text.len(), cx)
7398 });
7399
7400 fake_server
7401 .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
7402 Ok(Some(lsp::CompletionResponse::Array(vec![
7403 lsp::CompletionItem {
7404 label: "fullyQualifiedName?".into(),
7405 insert_text: Some("fullyQualifiedName".into()),
7406 ..Default::default()
7407 },
7408 ])))
7409 })
7410 .next()
7411 .await;
7412 let completions = completions.await.unwrap();
7413 let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
7414 assert_eq!(completions.len(), 1);
7415 assert_eq!(completions[0].new_text, "fullyQualifiedName");
7416 assert_eq!(
7417 completions[0].old_range.to_offset(&snapshot),
7418 text.len() - 3..text.len()
7419 );
7420 }
7421
7422 #[gpui::test(iterations = 10)]
7423 async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
7424 let mut language = Language::new(
7425 LanguageConfig {
7426 name: "TypeScript".into(),
7427 path_suffixes: vec!["ts".to_string()],
7428 ..Default::default()
7429 },
7430 None,
7431 );
7432 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
7433
7434 let fs = FakeFs::new(cx.background());
7435 fs.insert_tree(
7436 "/dir",
7437 json!({
7438 "a.ts": "a",
7439 }),
7440 )
7441 .await;
7442
7443 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
7444 project.update(cx, |project, _| project.languages.add(Arc::new(language)));
7445 let buffer = project
7446 .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
7447 .await
7448 .unwrap();
7449
7450 let fake_server = fake_language_servers.next().await.unwrap();
7451
7452 // Language server returns code actions that contain commands, and not edits.
7453 let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx));
7454 fake_server
7455 .handle_request::<lsp::request::CodeActionRequest, _, _>(|_, _| async move {
7456 Ok(Some(vec![
7457 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
7458 title: "The code action".into(),
7459 command: Some(lsp::Command {
7460 title: "The command".into(),
7461 command: "_the/command".into(),
7462 arguments: Some(vec![json!("the-argument")]),
7463 }),
7464 ..Default::default()
7465 }),
7466 lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
7467 title: "two".into(),
7468 ..Default::default()
7469 }),
7470 ]))
7471 })
7472 .next()
7473 .await;
7474
7475 let action = actions.await.unwrap()[0].clone();
7476 let apply = project.update(cx, |project, cx| {
7477 project.apply_code_action(buffer.clone(), action, true, cx)
7478 });
7479
7480 // Resolving the code action does not populate its edits. In absence of
7481 // edits, we must execute the given command.
7482 fake_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
7483 |action, _| async move { Ok(action) },
7484 );
7485
7486 // While executing the command, the language server sends the editor
7487 // a `workspaceEdit` request.
7488 fake_server
7489 .handle_request::<lsp::request::ExecuteCommand, _, _>({
7490 let fake = fake_server.clone();
7491 move |params, _| {
7492 assert_eq!(params.command, "_the/command");
7493 let fake = fake.clone();
7494 async move {
7495 fake.server
7496 .request::<lsp::request::ApplyWorkspaceEdit>(
7497 lsp::ApplyWorkspaceEditParams {
7498 label: None,
7499 edit: lsp::WorkspaceEdit {
7500 changes: Some(
7501 [(
7502 lsp::Url::from_file_path("/dir/a.ts").unwrap(),
7503 vec![lsp::TextEdit {
7504 range: lsp::Range::new(
7505 lsp::Position::new(0, 0),
7506 lsp::Position::new(0, 0),
7507 ),
7508 new_text: "X".into(),
7509 }],
7510 )]
7511 .into_iter()
7512 .collect(),
7513 ),
7514 ..Default::default()
7515 },
7516 },
7517 )
7518 .await
7519 .unwrap();
7520 Ok(Some(json!(null)))
7521 }
7522 }
7523 })
7524 .next()
7525 .await;
7526
7527 // Applying the code action returns a project transaction containing the edits
7528 // sent by the language server in its `workspaceEdit` request.
7529 let transaction = apply.await.unwrap();
7530 assert!(transaction.0.contains_key(&buffer));
7531 buffer.update(cx, |buffer, cx| {
7532 assert_eq!(buffer.text(), "Xa");
7533 buffer.undo(cx);
7534 assert_eq!(buffer.text(), "a");
7535 });
7536 }
7537
7538 #[gpui::test]
7539 async fn test_save_file(cx: &mut gpui::TestAppContext) {
7540 let fs = FakeFs::new(cx.background());
7541 fs.insert_tree(
7542 "/dir",
7543 json!({
7544 "file1": "the old contents",
7545 }),
7546 )
7547 .await;
7548
7549 let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
7550 let buffer = project
7551 .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
7552 .await
7553 .unwrap();
7554 buffer
7555 .update(cx, |buffer, cx| {
7556 assert_eq!(buffer.text(), "the old contents");
7557 buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx);
7558 buffer.save(cx)
7559 })
7560 .await
7561 .unwrap();
7562
7563 let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
7564 assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
7565 }
7566
7567 #[gpui::test]
7568 async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
7569 let fs = FakeFs::new(cx.background());
7570 fs.insert_tree(
7571 "/dir",
7572 json!({
7573 "file1": "the old contents",
7574 }),
7575 )
7576 .await;
7577
7578 let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await;
7579 let buffer = project
7580 .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
7581 .await
7582 .unwrap();
7583 buffer
7584 .update(cx, |buffer, cx| {
7585 buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx);
7586 buffer.save(cx)
7587 })
7588 .await
7589 .unwrap();
7590
7591 let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
7592 assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
7593 }
7594
7595 #[gpui::test]
7596 async fn test_save_as(cx: &mut gpui::TestAppContext) {
7597 let fs = FakeFs::new(cx.background());
7598 fs.insert_tree("/dir", json!({})).await;
7599
7600 let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
7601 let buffer = project.update(cx, |project, cx| {
7602 project.create_buffer("", None, cx).unwrap()
7603 });
7604 buffer.update(cx, |buffer, cx| {
7605 buffer.edit([(0..0, "abc")], cx);
7606 assert!(buffer.is_dirty());
7607 assert!(!buffer.has_conflict());
7608 });
7609 project
7610 .update(cx, |project, cx| {
7611 project.save_buffer_as(buffer.clone(), "/dir/file1".into(), cx)
7612 })
7613 .await
7614 .unwrap();
7615 assert_eq!(fs.load(Path::new("/dir/file1")).await.unwrap(), "abc");
7616 buffer.read_with(cx, |buffer, cx| {
7617 assert_eq!(buffer.file().unwrap().full_path(cx), Path::new("dir/file1"));
7618 assert!(!buffer.is_dirty());
7619 assert!(!buffer.has_conflict());
7620 });
7621
7622 let opened_buffer = project
7623 .update(cx, |project, cx| {
7624 project.open_local_buffer("/dir/file1", cx)
7625 })
7626 .await
7627 .unwrap();
7628 assert_eq!(opened_buffer, buffer);
7629 }
7630
7631 #[gpui::test(retries = 5)]
7632 async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
7633 let dir = temp_tree(json!({
7634 "a": {
7635 "file1": "",
7636 "file2": "",
7637 "file3": "",
7638 },
7639 "b": {
7640 "c": {
7641 "file4": "",
7642 "file5": "",
7643 }
7644 }
7645 }));
7646
7647 let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await;
7648 let rpc = project.read_with(cx, |p, _| p.client.clone());
7649
7650 let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
7651 let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx));
7652 async move { buffer.await.unwrap() }
7653 };
7654 let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
7655 project.read_with(cx, |project, cx| {
7656 let tree = project.worktrees(cx).next().unwrap();
7657 tree.read(cx)
7658 .entry_for_path(path)
7659 .expect(&format!("no entry for path {}", path))
7660 .id
7661 })
7662 };
7663
7664 let buffer2 = buffer_for_path("a/file2", cx).await;
7665 let buffer3 = buffer_for_path("a/file3", cx).await;
7666 let buffer4 = buffer_for_path("b/c/file4", cx).await;
7667 let buffer5 = buffer_for_path("b/c/file5", cx).await;
7668
7669 let file2_id = id_for_path("a/file2", &cx);
7670 let file3_id = id_for_path("a/file3", &cx);
7671 let file4_id = id_for_path("b/c/file4", &cx);
7672
7673 // Create a remote copy of this worktree.
7674 let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
7675 let initial_snapshot = tree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
7676 let (remote, load_task) = cx.update(|cx| {
7677 Worktree::remote(
7678 1,
7679 1,
7680 initial_snapshot.to_proto(&Default::default(), true),
7681 rpc.clone(),
7682 cx,
7683 )
7684 });
7685 // tree
7686 load_task.await;
7687
7688 cx.read(|cx| {
7689 assert!(!buffer2.read(cx).is_dirty());
7690 assert!(!buffer3.read(cx).is_dirty());
7691 assert!(!buffer4.read(cx).is_dirty());
7692 assert!(!buffer5.read(cx).is_dirty());
7693 });
7694
7695 // Rename and delete files and directories.
7696 tree.flush_fs_events(&cx).await;
7697 std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
7698 std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
7699 std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
7700 std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
7701 tree.flush_fs_events(&cx).await;
7702
7703 let expected_paths = vec![
7704 "a",
7705 "a/file1",
7706 "a/file2.new",
7707 "b",
7708 "d",
7709 "d/file3",
7710 "d/file4",
7711 ];
7712
7713 cx.read(|app| {
7714 assert_eq!(
7715 tree.read(app)
7716 .paths()
7717 .map(|p| p.to_str().unwrap())
7718 .collect::<Vec<_>>(),
7719 expected_paths
7720 );
7721
7722 assert_eq!(id_for_path("a/file2.new", &cx), file2_id);
7723 assert_eq!(id_for_path("d/file3", &cx), file3_id);
7724 assert_eq!(id_for_path("d/file4", &cx), file4_id);
7725
7726 assert_eq!(
7727 buffer2.read(app).file().unwrap().path().as_ref(),
7728 Path::new("a/file2.new")
7729 );
7730 assert_eq!(
7731 buffer3.read(app).file().unwrap().path().as_ref(),
7732 Path::new("d/file3")
7733 );
7734 assert_eq!(
7735 buffer4.read(app).file().unwrap().path().as_ref(),
7736 Path::new("d/file4")
7737 );
7738 assert_eq!(
7739 buffer5.read(app).file().unwrap().path().as_ref(),
7740 Path::new("b/c/file5")
7741 );
7742
7743 assert!(!buffer2.read(app).file().unwrap().is_deleted());
7744 assert!(!buffer3.read(app).file().unwrap().is_deleted());
7745 assert!(!buffer4.read(app).file().unwrap().is_deleted());
7746 assert!(buffer5.read(app).file().unwrap().is_deleted());
7747 });
7748
7749 // Update the remote worktree. Check that it becomes consistent with the
7750 // local worktree.
7751 remote.update(cx, |remote, cx| {
7752 let update_message = tree.read(cx).as_local().unwrap().snapshot().build_update(
7753 &initial_snapshot,
7754 1,
7755 1,
7756 true,
7757 );
7758 remote
7759 .as_remote_mut()
7760 .unwrap()
7761 .snapshot
7762 .apply_remote_update(update_message)
7763 .unwrap();
7764
7765 assert_eq!(
7766 remote
7767 .paths()
7768 .map(|p| p.to_str().unwrap())
7769 .collect::<Vec<_>>(),
7770 expected_paths
7771 );
7772 });
7773 }
7774
7775 #[gpui::test]
7776 async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
7777 let fs = FakeFs::new(cx.background());
7778 fs.insert_tree(
7779 "/dir",
7780 json!({
7781 "a.txt": "a-contents",
7782 "b.txt": "b-contents",
7783 }),
7784 )
7785 .await;
7786
7787 let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
7788
7789 // Spawn multiple tasks to open paths, repeating some paths.
7790 let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| {
7791 (
7792 p.open_local_buffer("/dir/a.txt", cx),
7793 p.open_local_buffer("/dir/b.txt", cx),
7794 p.open_local_buffer("/dir/a.txt", cx),
7795 )
7796 });
7797
7798 let buffer_a_1 = buffer_a_1.await.unwrap();
7799 let buffer_a_2 = buffer_a_2.await.unwrap();
7800 let buffer_b = buffer_b.await.unwrap();
7801 assert_eq!(buffer_a_1.read_with(cx, |b, _| b.text()), "a-contents");
7802 assert_eq!(buffer_b.read_with(cx, |b, _| b.text()), "b-contents");
7803
7804 // There is only one buffer per path.
7805 let buffer_a_id = buffer_a_1.id();
7806 assert_eq!(buffer_a_2.id(), buffer_a_id);
7807
7808 // Open the same path again while it is still open.
7809 drop(buffer_a_1);
7810 let buffer_a_3 = project
7811 .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx))
7812 .await
7813 .unwrap();
7814
7815 // There's still only one buffer per path.
7816 assert_eq!(buffer_a_3.id(), buffer_a_id);
7817 }
7818
7819 #[gpui::test]
7820 async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
7821 let fs = FakeFs::new(cx.background());
7822 fs.insert_tree(
7823 "/dir",
7824 json!({
7825 "file1": "abc",
7826 "file2": "def",
7827 "file3": "ghi",
7828 }),
7829 )
7830 .await;
7831
7832 let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
7833
7834 let buffer1 = project
7835 .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
7836 .await
7837 .unwrap();
7838 let events = Rc::new(RefCell::new(Vec::new()));
7839
7840 // initially, the buffer isn't dirty.
7841 buffer1.update(cx, |buffer, cx| {
7842 cx.subscribe(&buffer1, {
7843 let events = events.clone();
7844 move |_, _, event, _| match event {
7845 BufferEvent::Operation(_) => {}
7846 _ => events.borrow_mut().push(event.clone()),
7847 }
7848 })
7849 .detach();
7850
7851 assert!(!buffer.is_dirty());
7852 assert!(events.borrow().is_empty());
7853
7854 buffer.edit([(1..2, "")], cx);
7855 });
7856
7857 // after the first edit, the buffer is dirty, and emits a dirtied event.
7858 buffer1.update(cx, |buffer, cx| {
7859 assert!(buffer.text() == "ac");
7860 assert!(buffer.is_dirty());
7861 assert_eq!(
7862 *events.borrow(),
7863 &[language::Event::Edited, language::Event::Dirtied]
7864 );
7865 events.borrow_mut().clear();
7866 buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
7867 });
7868
7869 // after saving, the buffer is not dirty, and emits a saved event.
7870 buffer1.update(cx, |buffer, cx| {
7871 assert!(!buffer.is_dirty());
7872 assert_eq!(*events.borrow(), &[language::Event::Saved]);
7873 events.borrow_mut().clear();
7874
7875 buffer.edit([(1..1, "B")], cx);
7876 buffer.edit([(2..2, "D")], cx);
7877 });
7878
7879 // after editing again, the buffer is dirty, and emits another dirty event.
7880 buffer1.update(cx, |buffer, cx| {
7881 assert!(buffer.text() == "aBDc");
7882 assert!(buffer.is_dirty());
7883 assert_eq!(
7884 *events.borrow(),
7885 &[
7886 language::Event::Edited,
7887 language::Event::Dirtied,
7888 language::Event::Edited,
7889 ],
7890 );
7891 events.borrow_mut().clear();
7892
7893 // TODO - currently, after restoring the buffer to its
7894 // previously-saved state, the is still considered dirty.
7895 buffer.edit([(1..3, "")], cx);
7896 assert!(buffer.text() == "ac");
7897 assert!(buffer.is_dirty());
7898 });
7899
7900 assert_eq!(*events.borrow(), &[language::Event::Edited]);
7901
7902 // When a file is deleted, the buffer is considered dirty.
7903 let events = Rc::new(RefCell::new(Vec::new()));
7904 let buffer2 = project
7905 .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
7906 .await
7907 .unwrap();
7908 buffer2.update(cx, |_, cx| {
7909 cx.subscribe(&buffer2, {
7910 let events = events.clone();
7911 move |_, _, event, _| events.borrow_mut().push(event.clone())
7912 })
7913 .detach();
7914 });
7915
7916 fs.remove_file("/dir/file2".as_ref(), Default::default())
7917 .await
7918 .unwrap();
7919 buffer2.condition(&cx, |b, _| b.is_dirty()).await;
7920 assert_eq!(
7921 *events.borrow(),
7922 &[language::Event::Dirtied, language::Event::FileHandleChanged]
7923 );
7924
7925 // When a file is already dirty when deleted, we don't emit a Dirtied event.
7926 let events = Rc::new(RefCell::new(Vec::new()));
7927 let buffer3 = project
7928 .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx))
7929 .await
7930 .unwrap();
7931 buffer3.update(cx, |_, cx| {
7932 cx.subscribe(&buffer3, {
7933 let events = events.clone();
7934 move |_, _, event, _| events.borrow_mut().push(event.clone())
7935 })
7936 .detach();
7937 });
7938
7939 buffer3.update(cx, |buffer, cx| {
7940 buffer.edit([(0..0, "x")], cx);
7941 });
7942 events.borrow_mut().clear();
7943 fs.remove_file("/dir/file3".as_ref(), Default::default())
7944 .await
7945 .unwrap();
7946 buffer3
7947 .condition(&cx, |_, _| !events.borrow().is_empty())
7948 .await;
7949 assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]);
7950 cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
7951 }
7952
7953 #[gpui::test]
7954 async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
7955 let initial_contents = "aaa\nbbbbb\nc\n";
7956 let fs = FakeFs::new(cx.background());
7957 fs.insert_tree(
7958 "/dir",
7959 json!({
7960 "the-file": initial_contents,
7961 }),
7962 )
7963 .await;
7964 let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
7965 let buffer = project
7966 .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx))
7967 .await
7968 .unwrap();
7969
7970 let anchors = (0..3)
7971 .map(|row| buffer.read_with(cx, |b, _| b.anchor_before(Point::new(row, 1))))
7972 .collect::<Vec<_>>();
7973
7974 // Change the file on disk, adding two new lines of text, and removing
7975 // one line.
7976 buffer.read_with(cx, |buffer, _| {
7977 assert!(!buffer.is_dirty());
7978 assert!(!buffer.has_conflict());
7979 });
7980 let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
7981 fs.save("/dir/the-file".as_ref(), &new_contents.into())
7982 .await
7983 .unwrap();
7984
7985 // Because the buffer was not modified, it is reloaded from disk. Its
7986 // contents are edited according to the diff between the old and new
7987 // file contents.
7988 buffer
7989 .condition(&cx, |buffer, _| buffer.text() == new_contents)
7990 .await;
7991
7992 buffer.update(cx, |buffer, _| {
7993 assert_eq!(buffer.text(), new_contents);
7994 assert!(!buffer.is_dirty());
7995 assert!(!buffer.has_conflict());
7996
7997 let anchor_positions = anchors
7998 .iter()
7999 .map(|anchor| anchor.to_point(&*buffer))
8000 .collect::<Vec<_>>();
8001 assert_eq!(
8002 anchor_positions,
8003 [Point::new(1, 1), Point::new(3, 1), Point::new(4, 0)]
8004 );
8005 });
8006
8007 // Modify the buffer
8008 buffer.update(cx, |buffer, cx| {
8009 buffer.edit([(0..0, " ")], cx);
8010 assert!(buffer.is_dirty());
8011 assert!(!buffer.has_conflict());
8012 });
8013
8014 // Change the file on disk again, adding blank lines to the beginning.
8015 fs.save(
8016 "/dir/the-file".as_ref(),
8017 &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(),
8018 )
8019 .await
8020 .unwrap();
8021
8022 // Because the buffer is modified, it doesn't reload from disk, but is
8023 // marked as having a conflict.
8024 buffer
8025 .condition(&cx, |buffer, _| buffer.has_conflict())
8026 .await;
8027 }
8028
8029 #[gpui::test]
8030 async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
8031 cx.foreground().forbid_parking();
8032
8033 let fs = FakeFs::new(cx.background());
8034 fs.insert_tree(
8035 "/the-dir",
8036 json!({
8037 "a.rs": "
8038 fn foo(mut v: Vec<usize>) {
8039 for x in &v {
8040 v.push(1);
8041 }
8042 }
8043 "
8044 .unindent(),
8045 }),
8046 )
8047 .await;
8048
8049 let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await;
8050 let buffer = project
8051 .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx))
8052 .await
8053 .unwrap();
8054
8055 let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
8056 let message = lsp::PublishDiagnosticsParams {
8057 uri: buffer_uri.clone(),
8058 diagnostics: vec![
8059 lsp::Diagnostic {
8060 range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
8061 severity: Some(DiagnosticSeverity::WARNING),
8062 message: "error 1".to_string(),
8063 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
8064 location: lsp::Location {
8065 uri: buffer_uri.clone(),
8066 range: lsp::Range::new(
8067 lsp::Position::new(1, 8),
8068 lsp::Position::new(1, 9),
8069 ),
8070 },
8071 message: "error 1 hint 1".to_string(),
8072 }]),
8073 ..Default::default()
8074 },
8075 lsp::Diagnostic {
8076 range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
8077 severity: Some(DiagnosticSeverity::HINT),
8078 message: "error 1 hint 1".to_string(),
8079 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
8080 location: lsp::Location {
8081 uri: buffer_uri.clone(),
8082 range: lsp::Range::new(
8083 lsp::Position::new(1, 8),
8084 lsp::Position::new(1, 9),
8085 ),
8086 },
8087 message: "original diagnostic".to_string(),
8088 }]),
8089 ..Default::default()
8090 },
8091 lsp::Diagnostic {
8092 range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
8093 severity: Some(DiagnosticSeverity::ERROR),
8094 message: "error 2".to_string(),
8095 related_information: Some(vec![
8096 lsp::DiagnosticRelatedInformation {
8097 location: lsp::Location {
8098 uri: buffer_uri.clone(),
8099 range: lsp::Range::new(
8100 lsp::Position::new(1, 13),
8101 lsp::Position::new(1, 15),
8102 ),
8103 },
8104 message: "error 2 hint 1".to_string(),
8105 },
8106 lsp::DiagnosticRelatedInformation {
8107 location: lsp::Location {
8108 uri: buffer_uri.clone(),
8109 range: lsp::Range::new(
8110 lsp::Position::new(1, 13),
8111 lsp::Position::new(1, 15),
8112 ),
8113 },
8114 message: "error 2 hint 2".to_string(),
8115 },
8116 ]),
8117 ..Default::default()
8118 },
8119 lsp::Diagnostic {
8120 range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
8121 severity: Some(DiagnosticSeverity::HINT),
8122 message: "error 2 hint 1".to_string(),
8123 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
8124 location: lsp::Location {
8125 uri: buffer_uri.clone(),
8126 range: lsp::Range::new(
8127 lsp::Position::new(2, 8),
8128 lsp::Position::new(2, 17),
8129 ),
8130 },
8131 message: "original diagnostic".to_string(),
8132 }]),
8133 ..Default::default()
8134 },
8135 lsp::Diagnostic {
8136 range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
8137 severity: Some(DiagnosticSeverity::HINT),
8138 message: "error 2 hint 2".to_string(),
8139 related_information: Some(vec![lsp::DiagnosticRelatedInformation {
8140 location: lsp::Location {
8141 uri: buffer_uri.clone(),
8142 range: lsp::Range::new(
8143 lsp::Position::new(2, 8),
8144 lsp::Position::new(2, 17),
8145 ),
8146 },
8147 message: "original diagnostic".to_string(),
8148 }]),
8149 ..Default::default()
8150 },
8151 ],
8152 version: None,
8153 };
8154
8155 project
8156 .update(cx, |p, cx| p.update_diagnostics(0, message, &[], cx))
8157 .unwrap();
8158 let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot());
8159
8160 assert_eq!(
8161 buffer
8162 .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
8163 .collect::<Vec<_>>(),
8164 &[
8165 DiagnosticEntry {
8166 range: Point::new(1, 8)..Point::new(1, 9),
8167 diagnostic: Diagnostic {
8168 severity: DiagnosticSeverity::WARNING,
8169 message: "error 1".to_string(),
8170 group_id: 0,
8171 is_primary: true,
8172 ..Default::default()
8173 }
8174 },
8175 DiagnosticEntry {
8176 range: Point::new(1, 8)..Point::new(1, 9),
8177 diagnostic: Diagnostic {
8178 severity: DiagnosticSeverity::HINT,
8179 message: "error 1 hint 1".to_string(),
8180 group_id: 0,
8181 is_primary: false,
8182 ..Default::default()
8183 }
8184 },
8185 DiagnosticEntry {
8186 range: Point::new(1, 13)..Point::new(1, 15),
8187 diagnostic: Diagnostic {
8188 severity: DiagnosticSeverity::HINT,
8189 message: "error 2 hint 1".to_string(),
8190 group_id: 1,
8191 is_primary: false,
8192 ..Default::default()
8193 }
8194 },
8195 DiagnosticEntry {
8196 range: Point::new(1, 13)..Point::new(1, 15),
8197 diagnostic: Diagnostic {
8198 severity: DiagnosticSeverity::HINT,
8199 message: "error 2 hint 2".to_string(),
8200 group_id: 1,
8201 is_primary: false,
8202 ..Default::default()
8203 }
8204 },
8205 DiagnosticEntry {
8206 range: Point::new(2, 8)..Point::new(2, 17),
8207 diagnostic: Diagnostic {
8208 severity: DiagnosticSeverity::ERROR,
8209 message: "error 2".to_string(),
8210 group_id: 1,
8211 is_primary: true,
8212 ..Default::default()
8213 }
8214 }
8215 ]
8216 );
8217
8218 assert_eq!(
8219 buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
8220 &[
8221 DiagnosticEntry {
8222 range: Point::new(1, 8)..Point::new(1, 9),
8223 diagnostic: Diagnostic {
8224 severity: DiagnosticSeverity::WARNING,
8225 message: "error 1".to_string(),
8226 group_id: 0,
8227 is_primary: true,
8228 ..Default::default()
8229 }
8230 },
8231 DiagnosticEntry {
8232 range: Point::new(1, 8)..Point::new(1, 9),
8233 diagnostic: Diagnostic {
8234 severity: DiagnosticSeverity::HINT,
8235 message: "error 1 hint 1".to_string(),
8236 group_id: 0,
8237 is_primary: false,
8238 ..Default::default()
8239 }
8240 },
8241 ]
8242 );
8243 assert_eq!(
8244 buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
8245 &[
8246 DiagnosticEntry {
8247 range: Point::new(1, 13)..Point::new(1, 15),
8248 diagnostic: Diagnostic {
8249 severity: DiagnosticSeverity::HINT,
8250 message: "error 2 hint 1".to_string(),
8251 group_id: 1,
8252 is_primary: false,
8253 ..Default::default()
8254 }
8255 },
8256 DiagnosticEntry {
8257 range: Point::new(1, 13)..Point::new(1, 15),
8258 diagnostic: Diagnostic {
8259 severity: DiagnosticSeverity::HINT,
8260 message: "error 2 hint 2".to_string(),
8261 group_id: 1,
8262 is_primary: false,
8263 ..Default::default()
8264 }
8265 },
8266 DiagnosticEntry {
8267 range: Point::new(2, 8)..Point::new(2, 17),
8268 diagnostic: Diagnostic {
8269 severity: DiagnosticSeverity::ERROR,
8270 message: "error 2".to_string(),
8271 group_id: 1,
8272 is_primary: true,
8273 ..Default::default()
8274 }
8275 }
8276 ]
8277 );
8278 }
8279
8280 #[gpui::test]
8281 async fn test_rename(cx: &mut gpui::TestAppContext) {
8282 cx.foreground().forbid_parking();
8283
8284 let mut language = Language::new(
8285 LanguageConfig {
8286 name: "Rust".into(),
8287 path_suffixes: vec!["rs".to_string()],
8288 ..Default::default()
8289 },
8290 Some(tree_sitter_rust::language()),
8291 );
8292 let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
8293 capabilities: lsp::ServerCapabilities {
8294 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
8295 prepare_provider: Some(true),
8296 work_done_progress_options: Default::default(),
8297 })),
8298 ..Default::default()
8299 },
8300 ..Default::default()
8301 });
8302
8303 let fs = FakeFs::new(cx.background());
8304 fs.insert_tree(
8305 "/dir",
8306 json!({
8307 "one.rs": "const ONE: usize = 1;",
8308 "two.rs": "const TWO: usize = one::ONE + one::ONE;"
8309 }),
8310 )
8311 .await;
8312
8313 let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
8314 project.update(cx, |project, _| project.languages.add(Arc::new(language)));
8315 let buffer = project
8316 .update(cx, |project, cx| {
8317 project.open_local_buffer("/dir/one.rs", cx)
8318 })
8319 .await
8320 .unwrap();
8321
8322 let fake_server = fake_servers.next().await.unwrap();
8323
8324 let response = project.update(cx, |project, cx| {
8325 project.prepare_rename(buffer.clone(), 7, cx)
8326 });
8327 fake_server
8328 .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
8329 assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
8330 assert_eq!(params.position, lsp::Position::new(0, 7));
8331 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
8332 lsp::Position::new(0, 6),
8333 lsp::Position::new(0, 9),
8334 ))))
8335 })
8336 .next()
8337 .await
8338 .unwrap();
8339 let range = response.await.unwrap().unwrap();
8340 let range = buffer.read_with(cx, |buffer, _| range.to_offset(buffer));
8341 assert_eq!(range, 6..9);
8342
8343 let response = project.update(cx, |project, cx| {
8344 project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
8345 });
8346 fake_server
8347 .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
8348 assert_eq!(
8349 params.text_document_position.text_document.uri.as_str(),
8350 "file:///dir/one.rs"
8351 );
8352 assert_eq!(
8353 params.text_document_position.position,
8354 lsp::Position::new(0, 7)
8355 );
8356 assert_eq!(params.new_name, "THREE");
8357 Ok(Some(lsp::WorkspaceEdit {
8358 changes: Some(
8359 [
8360 (
8361 lsp::Url::from_file_path("/dir/one.rs").unwrap(),
8362 vec![lsp::TextEdit::new(
8363 lsp::Range::new(
8364 lsp::Position::new(0, 6),
8365 lsp::Position::new(0, 9),
8366 ),
8367 "THREE".to_string(),
8368 )],
8369 ),
8370 (
8371 lsp::Url::from_file_path("/dir/two.rs").unwrap(),
8372 vec![
8373 lsp::TextEdit::new(
8374 lsp::Range::new(
8375 lsp::Position::new(0, 24),
8376 lsp::Position::new(0, 27),
8377 ),
8378 "THREE".to_string(),
8379 ),
8380 lsp::TextEdit::new(
8381 lsp::Range::new(
8382 lsp::Position::new(0, 35),
8383 lsp::Position::new(0, 38),
8384 ),
8385 "THREE".to_string(),
8386 ),
8387 ],
8388 ),
8389 ]
8390 .into_iter()
8391 .collect(),
8392 ),
8393 ..Default::default()
8394 }))
8395 })
8396 .next()
8397 .await
8398 .unwrap();
8399 let mut transaction = response.await.unwrap().0;
8400 assert_eq!(transaction.len(), 2);
8401 assert_eq!(
8402 transaction
8403 .remove_entry(&buffer)
8404 .unwrap()
8405 .0
8406 .read_with(cx, |buffer, _| buffer.text()),
8407 "const THREE: usize = 1;"
8408 );
8409 assert_eq!(
8410 transaction
8411 .into_keys()
8412 .next()
8413 .unwrap()
8414 .read_with(cx, |buffer, _| buffer.text()),
8415 "const TWO: usize = one::THREE + one::THREE;"
8416 );
8417 }
8418
8419 #[gpui::test]
8420 async fn test_search(cx: &mut gpui::TestAppContext) {
8421 let fs = FakeFs::new(cx.background());
8422 fs.insert_tree(
8423 "/dir",
8424 json!({
8425 "one.rs": "const ONE: usize = 1;",
8426 "two.rs": "const TWO: usize = one::ONE + one::ONE;",
8427 "three.rs": "const THREE: usize = one::ONE + two::TWO;",
8428 "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
8429 }),
8430 )
8431 .await;
8432 let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
8433 assert_eq!(
8434 search(&project, SearchQuery::text("TWO", false, true), cx)
8435 .await
8436 .unwrap(),
8437 HashMap::from_iter([
8438 ("two.rs".to_string(), vec![6..9]),
8439 ("three.rs".to_string(), vec![37..40])
8440 ])
8441 );
8442
8443 let buffer_4 = project
8444 .update(cx, |project, cx| {
8445 project.open_local_buffer("/dir/four.rs", cx)
8446 })
8447 .await
8448 .unwrap();
8449 buffer_4.update(cx, |buffer, cx| {
8450 let text = "two::TWO";
8451 buffer.edit([(20..28, text), (31..43, text)], cx);
8452 });
8453
8454 assert_eq!(
8455 search(&project, SearchQuery::text("TWO", false, true), cx)
8456 .await
8457 .unwrap(),
8458 HashMap::from_iter([
8459 ("two.rs".to_string(), vec![6..9]),
8460 ("three.rs".to_string(), vec![37..40]),
8461 ("four.rs".to_string(), vec![25..28, 36..39])
8462 ])
8463 );
8464
8465 async fn search(
8466 project: &ModelHandle<Project>,
8467 query: SearchQuery,
8468 cx: &mut gpui::TestAppContext,
8469 ) -> Result<HashMap<String, Vec<Range<usize>>>> {
8470 let results = project
8471 .update(cx, |project, cx| project.search(query, cx))
8472 .await?;
8473
8474 Ok(results
8475 .into_iter()
8476 .map(|(buffer, ranges)| {
8477 buffer.read_with(cx, |buffer, _| {
8478 let path = buffer.file().unwrap().path().to_string_lossy().to_string();
8479 let ranges = ranges
8480 .into_iter()
8481 .map(|range| range.to_offset(buffer))
8482 .collect::<Vec<_>>();
8483 (path, ranges)
8484 })
8485 })
8486 .collect())
8487 }
8488 }
8489}