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