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