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