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