1mod char_bag;
2mod fuzzy;
3mod ignore;
4
5use self::{char_bag::CharBag, ignore::IgnoreStack};
6use crate::{
7 editor::{self, Buffer, History, Operation, Rope},
8 language::LanguageRegistry,
9 rpc::{self, proto},
10 sum_tree::{self, Cursor, Edit, SumTree},
11 time::ReplicaId,
12 util::Bias,
13};
14use ::ignore::gitignore::Gitignore;
15use anyhow::{anyhow, Context, Result};
16use atomic::Ordering::SeqCst;
17pub use fuzzy::{match_paths, PathMatch};
18use gpui::{
19 scoped_pool, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
20 Task, WeakModelHandle,
21};
22use lazy_static::lazy_static;
23use parking_lot::Mutex;
24use postage::{
25 prelude::{Sink, Stream},
26 watch,
27};
28use smol::{
29 channel::Sender,
30 io::{AsyncReadExt, AsyncWriteExt},
31};
32use std::{
33 cmp,
34 collections::HashMap,
35 ffi::{OsStr, OsString},
36 fmt, fs,
37 future::Future,
38 io,
39 ops::Deref,
40 os::unix::fs::MetadataExt,
41 path::{Path, PathBuf},
42 sync::{
43 atomic::{self, AtomicUsize},
44 Arc,
45 },
46 time::{Duration, SystemTime},
47};
48use zed_rpc::{PeerId, TypedEnvelope};
49
50lazy_static! {
51 static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
52}
53
54pub fn init(cx: &mut MutableAppContext, rpc: rpc::Client) {
55 rpc.on_message(remote::open_buffer, cx);
56 rpc.on_message(remote::close_buffer, cx);
57 rpc.on_message(remote::update_buffer, cx);
58 rpc.on_message(remote::remove_guest, cx);
59}
60
61#[derive(Clone, Debug)]
62enum ScanState {
63 Idle,
64 Scanning,
65 Err(Arc<io::Error>),
66}
67
68pub enum Worktree {
69 Local(LocalWorktree),
70 Remote(RemoteWorktree),
71}
72
73impl Entity for Worktree {
74 type Event = ();
75}
76
77impl Worktree {
78 pub fn local(
79 path: impl Into<Arc<Path>>,
80 languages: Arc<LanguageRegistry>,
81 cx: &mut ModelContext<Worktree>,
82 ) -> Self {
83 Worktree::Local(LocalWorktree::new(path, languages, cx))
84 }
85
86 pub async fn remote(
87 rpc: rpc::Client,
88 id: u64,
89 access_token: String,
90 languages: Arc<LanguageRegistry>,
91 cx: &mut AsyncAppContext,
92 ) -> Result<ModelHandle<Self>> {
93 let open_worktree_response = rpc
94 .request(proto::OpenWorktree {
95 worktree_id: id,
96 access_token,
97 })
98 .await?;
99 let worktree_message = open_worktree_response
100 .worktree
101 .ok_or_else(|| anyhow!("empty worktree"))?;
102 let replica_id = open_worktree_response
103 .replica_id
104 .ok_or_else(|| anyhow!("empty replica id"))?;
105 let worktree = cx.update(|cx| {
106 cx.add_model(|cx| {
107 Worktree::Remote(RemoteWorktree::new(
108 id,
109 worktree_message,
110 rpc.clone(),
111 replica_id as ReplicaId,
112 languages,
113 cx,
114 ))
115 })
116 });
117 rpc.state
118 .lock()
119 .await
120 .shared_worktrees
121 .insert(id, worktree.downgrade());
122 Ok(worktree)
123 }
124
125 pub fn as_local(&self) -> Option<&LocalWorktree> {
126 if let Worktree::Local(worktree) = self {
127 Some(worktree)
128 } else {
129 None
130 }
131 }
132
133 pub fn as_local_mut(&mut self) -> Option<&mut LocalWorktree> {
134 if let Worktree::Local(worktree) = self {
135 Some(worktree)
136 } else {
137 None
138 }
139 }
140
141 pub fn as_remote_mut(&mut self) -> Option<&mut RemoteWorktree> {
142 if let Worktree::Remote(worktree) = self {
143 Some(worktree)
144 } else {
145 None
146 }
147 }
148
149 pub fn snapshot(&self) -> Snapshot {
150 match self {
151 Worktree::Local(worktree) => worktree.snapshot(),
152 Worktree::Remote(worktree) => worktree.snapshot.clone(),
153 }
154 }
155
156 pub fn open_buffer(
157 &mut self,
158 path: impl AsRef<Path>,
159 cx: &mut ModelContext<Self>,
160 ) -> Task<Result<ModelHandle<Buffer>>> {
161 match self {
162 Worktree::Local(worktree) => worktree.open_buffer(path.as_ref(), cx),
163 Worktree::Remote(worktree) => worktree.open_buffer(path.as_ref(), cx),
164 }
165 }
166
167 pub fn has_open_buffer(&self, path: impl AsRef<Path>, cx: &AppContext) -> bool {
168 let open_buffers = match self {
169 Worktree::Local(worktree) => &worktree.open_buffers,
170 Worktree::Remote(worktree) => &worktree.open_buffers,
171 };
172
173 let path = path.as_ref();
174 open_buffers
175 .values()
176 .find(|buffer| {
177 if let Some(file) = buffer.upgrade(cx).and_then(|buffer| buffer.read(cx).file()) {
178 file.path.as_ref() == path
179 } else {
180 false
181 }
182 })
183 .is_some()
184 }
185
186 pub fn buffer(&self, id: u64, cx: &AppContext) -> Option<ModelHandle<Buffer>> {
187 let open_buffers = match self {
188 Worktree::Local(worktree) => &worktree.open_buffers,
189 Worktree::Remote(worktree) => &worktree.open_buffers,
190 };
191
192 open_buffers
193 .get(&(id as usize))
194 .and_then(|buffer| buffer.upgrade(cx))
195 }
196
197 pub fn buffers<'a>(
198 &'a self,
199 cx: &'a AppContext,
200 ) -> impl 'a + Iterator<Item = ModelHandle<Buffer>> {
201 let open_buffers = match self {
202 Worktree::Local(worktree) => &worktree.open_buffers,
203 Worktree::Remote(worktree) => &worktree.open_buffers,
204 };
205 open_buffers
206 .values()
207 .filter_map(move |buffer| buffer.upgrade(cx))
208 }
209
210 fn save(
211 &self,
212 path: &Path,
213 text: Rope,
214 cx: &mut ModelContext<Self>,
215 ) -> impl Future<Output = Result<()>> {
216 match self {
217 Worktree::Local(worktree) => {
218 let save = worktree.save(path, text, cx);
219 async move {
220 save.await?;
221 Ok(())
222 }
223 }
224 Worktree::Remote(_) => todo!(),
225 }
226 }
227}
228
229impl Deref for Worktree {
230 type Target = Snapshot;
231
232 fn deref(&self) -> &Self::Target {
233 match self {
234 Worktree::Local(worktree) => &worktree.snapshot,
235 Worktree::Remote(worktree) => &worktree.snapshot,
236 }
237 }
238}
239
240pub struct LocalWorktree {
241 snapshot: Snapshot,
242 background_snapshot: Arc<Mutex<Snapshot>>,
243 scan_state: (watch::Sender<ScanState>, watch::Receiver<ScanState>),
244 _event_stream_handle: fsevent::Handle,
245 poll_scheduled: bool,
246 rpc: Option<(rpc::Client, u64)>,
247 open_buffers: HashMap<usize, WeakModelHandle<Buffer>>,
248 shared_buffers: HashMap<PeerId, HashMap<u64, ModelHandle<Buffer>>>,
249 languages: Arc<LanguageRegistry>,
250}
251
252impl LocalWorktree {
253 fn new(
254 path: impl Into<Arc<Path>>,
255 languages: Arc<LanguageRegistry>,
256 cx: &mut ModelContext<Worktree>,
257 ) -> Self {
258 let abs_path = path.into();
259 let (scan_state_tx, scan_state_rx) = smol::channel::unbounded();
260 let id = cx.model_id();
261 let snapshot = Snapshot {
262 id,
263 scan_id: 0,
264 abs_path,
265 root_name: Default::default(),
266 root_char_bag: Default::default(),
267 ignores: Default::default(),
268 entries: Default::default(),
269 paths_by_id: Default::default(),
270 removed_entry_ids: Default::default(),
271 next_entry_id: Default::default(),
272 };
273 let (event_stream, event_stream_handle) =
274 fsevent::EventStream::new(&[snapshot.abs_path.as_ref()], Duration::from_millis(100));
275
276 let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
277
278 let tree = Self {
279 snapshot,
280 background_snapshot: background_snapshot.clone(),
281 scan_state: watch::channel_with(ScanState::Scanning),
282 _event_stream_handle: event_stream_handle,
283 poll_scheduled: false,
284 open_buffers: Default::default(),
285 shared_buffers: Default::default(),
286 rpc: None,
287 languages,
288 };
289
290 std::thread::spawn(move || {
291 let scanner = BackgroundScanner::new(background_snapshot, scan_state_tx, id);
292 scanner.run(event_stream)
293 });
294
295 cx.spawn(|this, mut cx| {
296 let this = this.downgrade();
297 async move {
298 while let Ok(scan_state) = scan_state_rx.recv().await {
299 let alive = cx.update(|cx| {
300 if let Some(handle) = this.upgrade(&cx) {
301 handle.update(cx, |this, cx| {
302 if let Worktree::Local(worktree) = this {
303 worktree.observe_scan_state(scan_state, cx)
304 } else {
305 unreachable!()
306 }
307 });
308 true
309 } else {
310 false
311 }
312 });
313
314 if !alive {
315 break;
316 }
317 }
318 }
319 })
320 .detach();
321
322 tree
323 }
324
325 pub fn open_buffer(
326 &mut self,
327 path: &Path,
328 cx: &mut ModelContext<Worktree>,
329 ) -> Task<Result<ModelHandle<Buffer>>> {
330 let handle = cx.handle();
331
332 // If there is already a buffer for the given path, then return it.
333 let mut existing_buffer = None;
334 self.open_buffers.retain(|_buffer_id, buffer| {
335 if let Some(buffer) = buffer.upgrade(cx.as_ref()) {
336 if let Some(file) = buffer.read(cx.as_ref()).file() {
337 if file.worktree_id() == handle.id() && file.path.as_ref() == path {
338 existing_buffer = Some(buffer);
339 }
340 }
341 true
342 } else {
343 false
344 }
345 });
346
347 let languages = self.languages.clone();
348 let path = Arc::from(path);
349 cx.spawn(|this, mut cx| async move {
350 if let Some(existing_buffer) = existing_buffer {
351 Ok(existing_buffer)
352 } else {
353 let (file, contents) = this
354 .update(&mut cx, |this, cx| this.as_local().unwrap().load(&path, cx))
355 .await?;
356 let language = languages.select_language(&path).cloned();
357 let buffer = cx.add_model(|cx| {
358 Buffer::from_history(0, History::new(contents.into()), Some(file), language, cx)
359 });
360 this.update(&mut cx, |this, _| {
361 let this = this
362 .as_local_mut()
363 .ok_or_else(|| anyhow!("must be a local worktree"))?;
364 this.open_buffers.insert(buffer.id(), buffer.downgrade());
365 Ok(buffer)
366 })
367 }
368 })
369 }
370
371 pub fn open_remote_buffer(
372 &mut self,
373 envelope: TypedEnvelope<proto::OpenBuffer>,
374 cx: &mut ModelContext<Worktree>,
375 ) -> Task<Result<proto::OpenBufferResponse>> {
376 let peer_id = envelope.original_sender_id();
377 let path = Path::new(&envelope.payload.path);
378
379 let buffer = self.open_buffer(path, cx);
380
381 cx.spawn(|this, mut cx| async move {
382 let buffer = buffer.await?;
383 this.update(&mut cx, |this, cx| {
384 this.as_local_mut()
385 .unwrap()
386 .shared_buffers
387 .entry(peer_id?)
388 .or_default()
389 .insert(buffer.id() as u64, buffer.clone());
390
391 Ok(proto::OpenBufferResponse {
392 buffer: Some(buffer.update(cx.as_mut(), |buffer, cx| buffer.to_proto(cx))),
393 })
394 })
395 })
396 }
397
398 pub fn close_remote_buffer(
399 &mut self,
400 envelope: TypedEnvelope<proto::CloseBuffer>,
401 cx: &mut ModelContext<Worktree>,
402 ) -> Result<()> {
403 if let Some(shared_buffers) = self.shared_buffers.get_mut(&envelope.original_sender_id()?) {
404 shared_buffers.remove(&envelope.payload.buffer_id);
405 }
406
407 Ok(())
408 }
409
410 pub fn remove_guest(
411 &mut self,
412 envelope: TypedEnvelope<proto::RemoveGuest>,
413 cx: &mut ModelContext<Worktree>,
414 ) -> Result<()> {
415 self.shared_buffers.remove(&envelope.original_sender_id()?);
416 Ok(())
417 }
418
419 pub fn scan_complete(&self) -> impl Future<Output = ()> {
420 let mut scan_state_rx = self.scan_state.1.clone();
421 async move {
422 let mut scan_state = Some(scan_state_rx.borrow().clone());
423 while let Some(ScanState::Scanning) = scan_state {
424 scan_state = scan_state_rx.recv().await;
425 }
426 }
427 }
428
429 fn observe_scan_state(&mut self, scan_state: ScanState, cx: &mut ModelContext<Worktree>) {
430 self.scan_state.0.blocking_send(scan_state).ok();
431 self.poll_snapshot(cx);
432 }
433
434 fn poll_snapshot(&mut self, cx: &mut ModelContext<Worktree>) {
435 self.snapshot = self.background_snapshot.lock().clone();
436 if self.is_scanning() {
437 if !self.poll_scheduled {
438 cx.spawn(|this, mut cx| async move {
439 smol::Timer::after(Duration::from_millis(100)).await;
440 this.update(&mut cx, |this, cx| {
441 let worktree = this.as_local_mut().unwrap();
442 worktree.poll_scheduled = false;
443 worktree.poll_snapshot(cx);
444 })
445 })
446 .detach();
447 self.poll_scheduled = true;
448 }
449 } else {
450 let mut buffers_to_delete = Vec::new();
451 for (buffer_id, buffer) in &self.open_buffers {
452 if let Some(buffer) = buffer.upgrade(&cx) {
453 buffer.update(cx, |buffer, cx| {
454 let buffer_is_clean = !buffer.is_dirty();
455
456 if let Some(file) = buffer.file_mut() {
457 let mut file_changed = false;
458
459 if let Some(entry) = file
460 .entry_id
461 .and_then(|entry_id| self.entry_for_id(entry_id))
462 {
463 if entry.path != file.path {
464 file.path = entry.path.clone();
465 file_changed = true;
466 }
467
468 if entry.mtime != file.mtime {
469 file.mtime = entry.mtime;
470 file_changed = true;
471 if buffer_is_clean {
472 let abs_path = self.absolutize(&file.path);
473 refresh_buffer(abs_path, cx);
474 }
475 }
476 } else if let Some(entry) = self.entry_for_path(&file.path) {
477 file.entry_id = Some(entry.id);
478 file.mtime = entry.mtime;
479 if buffer_is_clean {
480 let abs_path = self.absolutize(&file.path);
481 refresh_buffer(abs_path, cx);
482 }
483 file_changed = true;
484 } else if !file.is_deleted() {
485 if buffer_is_clean {
486 cx.emit(editor::buffer::Event::Dirtied);
487 }
488 file.entry_id = None;
489 file_changed = true;
490 }
491
492 if file_changed {
493 cx.emit(editor::buffer::Event::FileHandleChanged);
494 }
495 }
496 });
497 } else {
498 buffers_to_delete.push(*buffer_id);
499 }
500 }
501
502 for buffer_id in buffers_to_delete {
503 self.open_buffers.remove(&buffer_id);
504 }
505 }
506
507 cx.notify();
508 }
509
510 fn is_scanning(&self) -> bool {
511 if let ScanState::Scanning = *self.scan_state.1.borrow() {
512 true
513 } else {
514 false
515 }
516 }
517
518 pub fn snapshot(&self) -> Snapshot {
519 self.snapshot.clone()
520 }
521
522 pub fn abs_path(&self) -> &Path {
523 self.snapshot.abs_path.as_ref()
524 }
525
526 pub fn contains_abs_path(&self, path: &Path) -> bool {
527 path.starts_with(&self.snapshot.abs_path)
528 }
529
530 fn absolutize(&self, path: &Path) -> PathBuf {
531 if path.file_name().is_some() {
532 self.snapshot.abs_path.join(path)
533 } else {
534 self.snapshot.abs_path.to_path_buf()
535 }
536 }
537
538 fn load(&self, path: &Path, cx: &mut ModelContext<Worktree>) -> Task<Result<(File, String)>> {
539 let handle = cx.handle();
540 let path = Arc::from(path);
541 let abs_path = self.absolutize(&path);
542 let background_snapshot = self.background_snapshot.clone();
543 cx.spawn(|this, mut cx| async move {
544 let mut file = smol::fs::File::open(&abs_path).await?;
545 let mut text = String::new();
546 file.read_to_string(&mut text).await?;
547 // Eagerly populate the snapshot with an updated entry for the loaded file
548 let entry = refresh_entry(&background_snapshot, path, &abs_path)?;
549 this.update(&mut cx, |this, cx| {
550 let this = this.as_local_mut().unwrap();
551 this.poll_snapshot(cx);
552 });
553 Ok((File::new(entry.id, handle, entry.path, entry.mtime), text))
554 })
555 }
556
557 pub fn save_buffer_as(
558 &self,
559 buffer: ModelHandle<Buffer>,
560 path: impl Into<Arc<Path>>,
561 text: Rope,
562 cx: &mut ModelContext<Worktree>,
563 ) -> Task<Result<File>> {
564 let save = self.save(path, text, cx);
565 cx.spawn(|this, mut cx| async move {
566 let entry = save.await?;
567 this.update(&mut cx, |this, cx| {
568 this.as_local_mut()
569 .unwrap()
570 .open_buffers
571 .insert(buffer.id(), buffer.downgrade());
572 Ok(File::new(entry.id, cx.handle(), entry.path, entry.mtime))
573 })
574 })
575 }
576
577 fn save(
578 &self,
579 path: impl Into<Arc<Path>>,
580 text: Rope,
581 cx: &mut ModelContext<Worktree>,
582 ) -> Task<Result<Entry>> {
583 let path = path.into();
584 let abs_path = self.absolutize(&path);
585 let background_snapshot = self.background_snapshot.clone();
586
587 let save = cx.background().spawn(async move {
588 let buffer_size = text.summary().bytes.min(10 * 1024);
589 let file = smol::fs::File::create(&abs_path).await?;
590 let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
591 for chunk in text.chunks() {
592 writer.write_all(chunk.as_bytes()).await?;
593 }
594 writer.flush().await?;
595 refresh_entry(&background_snapshot, path.clone(), &abs_path)
596 });
597
598 cx.spawn(|this, mut cx| async move {
599 let entry = save.await?;
600 this.update(&mut cx, |this, cx| {
601 this.as_local_mut().unwrap().poll_snapshot(cx);
602 });
603 Ok(entry)
604 })
605 }
606
607 pub fn share(
608 &mut self,
609 rpc: rpc::Client,
610 cx: &mut ModelContext<Worktree>,
611 ) -> Task<anyhow::Result<(u64, String)>> {
612 let root_name = self.root_name.clone();
613 let snapshot = self.snapshot();
614 let handle = cx.handle();
615 cx.spawn(|this, mut cx| async move {
616 let entries = cx
617 .background()
618 .spawn(async move {
619 snapshot
620 .entries
621 .cursor::<(), ()>()
622 .map(|entry| proto::Entry {
623 id: entry.id as u64,
624 is_dir: entry.is_dir(),
625 path: entry.path.to_string_lossy().to_string(),
626 inode: entry.inode,
627 mtime: Some(entry.mtime.into()),
628 is_symlink: entry.is_symlink,
629 is_ignored: entry.is_ignored,
630 })
631 .collect()
632 })
633 .await;
634
635 let share_response = rpc
636 .request(proto::ShareWorktree {
637 worktree: Some(proto::Worktree { root_name, entries }),
638 })
639 .await?;
640
641 rpc.state
642 .lock()
643 .await
644 .shared_worktrees
645 .insert(share_response.worktree_id, handle.downgrade());
646
647 log::info!("sharing worktree {:?}", share_response);
648
649 this.update(&mut cx, |worktree, _| {
650 worktree.as_local_mut().unwrap().rpc = Some((rpc, share_response.worktree_id));
651 });
652 Ok((share_response.worktree_id, share_response.access_token))
653 })
654 }
655}
656
657pub fn refresh_buffer(abs_path: PathBuf, cx: &mut ModelContext<Buffer>) {
658 cx.spawn(|buffer, mut cx| async move {
659 let new_text = cx
660 .background()
661 .spawn(async move {
662 let mut file = smol::fs::File::open(&abs_path).await?;
663 let mut text = String::new();
664 file.read_to_string(&mut text).await?;
665 Ok::<_, anyhow::Error>(text.into())
666 })
667 .await;
668
669 match new_text {
670 Err(error) => log::error!("error refreshing buffer after file changed: {}", error),
671 Ok(new_text) => {
672 buffer
673 .update(&mut cx, |buffer, cx| {
674 buffer.set_text_from_disk(new_text, cx)
675 })
676 .await;
677 }
678 }
679 })
680 .detach()
681}
682
683impl Deref for LocalWorktree {
684 type Target = Snapshot;
685
686 fn deref(&self) -> &Self::Target {
687 &self.snapshot
688 }
689}
690
691impl fmt::Debug for LocalWorktree {
692 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
693 self.snapshot.fmt(f)
694 }
695}
696
697pub struct RemoteWorktree {
698 remote_id: u64,
699 snapshot: Snapshot,
700 rpc: rpc::Client,
701 replica_id: ReplicaId,
702 open_buffers: HashMap<usize, WeakModelHandle<Buffer>>,
703 languages: Arc<LanguageRegistry>,
704}
705
706impl RemoteWorktree {
707 fn new(
708 remote_id: u64,
709 worktree: proto::Worktree,
710 rpc: rpc::Client,
711 replica_id: ReplicaId,
712 languages: Arc<LanguageRegistry>,
713 cx: &mut ModelContext<Worktree>,
714 ) -> Self {
715 let root_char_bag: CharBag = worktree
716 .root_name
717 .chars()
718 .map(|c| c.to_ascii_lowercase())
719 .collect();
720 let mut entries = SumTree::new();
721 let mut paths_by_id = rpds::HashTrieMapSync::default();
722 for entry in worktree.entries {
723 if let Some(mtime) = entry.mtime {
724 let kind = if entry.is_dir {
725 EntryKind::Dir
726 } else {
727 let mut char_bag = root_char_bag.clone();
728 char_bag.extend(entry.path.chars().map(|c| c.to_ascii_lowercase()));
729 EntryKind::File(char_bag)
730 };
731 let path: Arc<Path> = Arc::from(Path::new(&entry.path));
732 entries.push(
733 Entry {
734 id: entry.id as usize,
735 kind,
736 path: path.clone(),
737 inode: entry.inode,
738 mtime: mtime.into(),
739 is_symlink: entry.is_symlink,
740 is_ignored: entry.is_ignored,
741 },
742 &(),
743 );
744 paths_by_id.insert_mut(entry.id as usize, path);
745 } else {
746 log::warn!("missing mtime in remote worktree entry {:?}", entry.path);
747 }
748 }
749 let snapshot = Snapshot {
750 id: cx.model_id(),
751 scan_id: 0,
752 abs_path: Path::new("").into(),
753 root_name: worktree.root_name,
754 root_char_bag,
755 ignores: Default::default(),
756 entries,
757 paths_by_id,
758 removed_entry_ids: Default::default(),
759 next_entry_id: Default::default(),
760 };
761 Self {
762 remote_id,
763 snapshot,
764 rpc,
765 replica_id,
766 open_buffers: Default::default(),
767 languages,
768 }
769 }
770
771 pub fn open_buffer(
772 &mut self,
773 path: &Path,
774 cx: &mut ModelContext<Worktree>,
775 ) -> Task<Result<ModelHandle<Buffer>>> {
776 let handle = cx.handle();
777 let mut existing_buffer = None;
778 self.open_buffers.retain(|_buffer_id, buffer| {
779 if let Some(buffer) = buffer.upgrade(cx.as_ref()) {
780 if let Some(file) = buffer.read(cx.as_ref()).file() {
781 if file.worktree_id() == handle.id() && file.path.as_ref() == path {
782 existing_buffer = Some(buffer);
783 }
784 }
785 true
786 } else {
787 false
788 }
789 });
790
791 let rpc = self.rpc.clone();
792 let languages = self.languages.clone();
793 let replica_id = self.replica_id;
794 let remote_worktree_id = self.remote_id;
795 let path = path.to_string_lossy().to_string();
796 cx.spawn(|this, mut cx| async move {
797 if let Some(existing_buffer) = existing_buffer {
798 Ok(existing_buffer)
799 } else {
800 let entry = this
801 .read_with(&cx, |tree, _| tree.entry_for_path(&path).cloned())
802 .ok_or_else(|| anyhow!("file does not exist"))?;
803 let file = File::new(entry.id, handle, entry.path, entry.mtime);
804 let language = languages.select_language(&path).cloned();
805 let response = rpc
806 .request(proto::OpenBuffer {
807 worktree_id: remote_worktree_id as u64,
808 path,
809 })
810 .await?;
811 let remote_buffer = response.buffer.ok_or_else(|| anyhow!("empty buffer"))?;
812 let buffer_id = remote_buffer.id;
813 let buffer = cx.add_model(|cx| {
814 Buffer::from_proto(replica_id, remote_buffer, Some(file), language, cx).unwrap()
815 });
816 this.update(&mut cx, |this, _| {
817 let this = this.as_remote_mut().unwrap();
818 this.open_buffers
819 .insert(buffer_id as usize, buffer.downgrade());
820 });
821 Ok(buffer)
822 }
823 })
824 }
825}
826
827#[derive(Clone)]
828pub struct Snapshot {
829 id: usize,
830 scan_id: usize,
831 abs_path: Arc<Path>,
832 root_name: String,
833 root_char_bag: CharBag,
834 ignores: HashMap<Arc<Path>, (Arc<Gitignore>, usize)>,
835 entries: SumTree<Entry>,
836 paths_by_id: rpds::HashTrieMapSync<usize, Arc<Path>>,
837 removed_entry_ids: HashMap<u64, usize>,
838 next_entry_id: Arc<AtomicUsize>,
839}
840
841impl Snapshot {
842 pub fn file_count(&self) -> usize {
843 self.entries.summary().file_count
844 }
845
846 pub fn visible_file_count(&self) -> usize {
847 self.entries.summary().visible_file_count
848 }
849
850 pub fn files(&self, start: usize) -> FileIter {
851 FileIter::all(self, start)
852 }
853
854 pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
855 let empty_path = Path::new("");
856 self.entries
857 .cursor::<(), ()>()
858 .filter(move |entry| entry.path.as_ref() != empty_path)
859 .map(|entry| entry.path())
860 }
861
862 pub fn visible_files(&self, start: usize) -> FileIter {
863 FileIter::visible(self, start)
864 }
865
866 fn child_entries<'a>(&'a self, path: &'a Path) -> ChildEntriesIter<'a> {
867 ChildEntriesIter::new(path, self)
868 }
869
870 pub fn root_entry(&self) -> &Entry {
871 self.entry_for_path("").unwrap()
872 }
873
874 /// Returns the filename of the snapshot's root, plus a trailing slash if the snapshot's root is
875 /// a directory.
876 pub fn root_name(&self) -> &str {
877 &self.root_name
878 }
879
880 fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
881 let mut cursor = self.entries.cursor::<_, ()>();
882 if cursor.seek(&PathSearch::Exact(path.as_ref()), Bias::Left, &()) {
883 cursor.item()
884 } else {
885 None
886 }
887 }
888
889 fn entry_for_id(&self, id: usize) -> Option<&Entry> {
890 let path = self.paths_by_id.get(&id)?;
891 self.entry_for_path(path)
892 }
893
894 pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
895 self.entry_for_path(path.as_ref()).map(|e| e.inode())
896 }
897
898 fn insert_entry(&mut self, mut entry: Entry) -> Entry {
899 if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
900 let (ignore, err) = Gitignore::new(self.abs_path.join(entry.path()));
901 if let Some(err) = err {
902 log::error!("error in ignore file {:?} - {:?}", entry.path(), err);
903 }
904
905 let ignore_dir_path = entry.path().parent().unwrap();
906 self.ignores
907 .insert(ignore_dir_path.into(), (Arc::new(ignore), self.scan_id));
908 }
909
910 self.reuse_entry_id(&mut entry);
911 self.entries.insert_or_replace(entry.clone(), &());
912 self.paths_by_id.insert_mut(entry.id, entry.path.clone());
913 entry
914 }
915
916 fn populate_dir(
917 &mut self,
918 parent_path: Arc<Path>,
919 entries: impl IntoIterator<Item = Entry>,
920 ignore: Option<Arc<Gitignore>>,
921 ) {
922 let mut edits = Vec::new();
923
924 let mut parent_entry = self
925 .entries
926 .get(&PathKey(parent_path.clone()), &())
927 .unwrap()
928 .clone();
929 if let Some(ignore) = ignore {
930 self.ignores.insert(parent_path, (ignore, self.scan_id));
931 }
932 if matches!(parent_entry.kind, EntryKind::PendingDir) {
933 parent_entry.kind = EntryKind::Dir;
934 } else {
935 unreachable!();
936 }
937 edits.push(Edit::Insert(parent_entry));
938
939 for mut entry in entries {
940 self.reuse_entry_id(&mut entry);
941 self.paths_by_id.insert_mut(entry.id, entry.path.clone());
942 edits.push(Edit::Insert(entry));
943 }
944 self.entries.edit(edits, &());
945 }
946
947 fn reuse_entry_id(&mut self, entry: &mut Entry) {
948 if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) {
949 entry.id = removed_entry_id;
950 } else if let Some(existing_entry) = self.entry_for_path(&entry.path) {
951 entry.id = existing_entry.id;
952 }
953 }
954
955 fn remove_path(&mut self, path: &Path) {
956 let mut new_entries;
957 let removed_entry_ids;
958 {
959 let mut cursor = self.entries.cursor::<_, ()>();
960 new_entries = cursor.slice(&PathSearch::Exact(path), Bias::Left, &());
961 removed_entry_ids = cursor.slice(&PathSearch::Successor(path), Bias::Left, &());
962 new_entries.push_tree(cursor.suffix(&()), &());
963 }
964 self.entries = new_entries;
965 for entry in removed_entry_ids.cursor::<(), ()>() {
966 let removed_entry_id = self
967 .removed_entry_ids
968 .entry(entry.inode)
969 .or_insert(entry.id);
970 *removed_entry_id = cmp::max(*removed_entry_id, entry.id);
971 self.paths_by_id.remove_mut(&entry.id);
972 }
973
974 if path.file_name() == Some(&GITIGNORE) {
975 if let Some((_, scan_id)) = self.ignores.get_mut(path.parent().unwrap()) {
976 *scan_id = self.scan_id;
977 }
978 }
979 }
980
981 fn ignore_stack_for_path(&self, path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
982 let mut new_ignores = Vec::new();
983 for ancestor in path.ancestors().skip(1) {
984 if let Some((ignore, _)) = self.ignores.get(ancestor) {
985 new_ignores.push((ancestor, Some(ignore.clone())));
986 } else {
987 new_ignores.push((ancestor, None));
988 }
989 }
990
991 let mut ignore_stack = IgnoreStack::none();
992 for (parent_path, ignore) in new_ignores.into_iter().rev() {
993 if ignore_stack.is_path_ignored(&parent_path, true) {
994 ignore_stack = IgnoreStack::all();
995 break;
996 } else if let Some(ignore) = ignore {
997 ignore_stack = ignore_stack.append(Arc::from(parent_path), ignore);
998 }
999 }
1000
1001 if ignore_stack.is_path_ignored(path, is_dir) {
1002 ignore_stack = IgnoreStack::all();
1003 }
1004
1005 ignore_stack
1006 }
1007}
1008
1009impl fmt::Debug for Snapshot {
1010 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1011 for entry in self.entries.cursor::<(), ()>() {
1012 for _ in entry.path().ancestors().skip(1) {
1013 write!(f, " ")?;
1014 }
1015 writeln!(f, "{:?} (inode: {})", entry.path(), entry.inode())?;
1016 }
1017 Ok(())
1018 }
1019}
1020
1021#[derive(Clone, PartialEq)]
1022pub struct File {
1023 entry_id: Option<usize>,
1024 worktree: ModelHandle<Worktree>,
1025 pub path: Arc<Path>,
1026 pub mtime: SystemTime,
1027}
1028
1029impl File {
1030 pub fn new(
1031 entry_id: usize,
1032 worktree: ModelHandle<Worktree>,
1033 path: Arc<Path>,
1034 mtime: SystemTime,
1035 ) -> Self {
1036 Self {
1037 entry_id: Some(entry_id),
1038 worktree,
1039 path,
1040 mtime,
1041 }
1042 }
1043
1044 pub fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) {
1045 self.worktree.update(cx, |worktree, cx| {
1046 if let Some((rpc, remote_id)) = match worktree {
1047 Worktree::Local(worktree) => worktree.rpc.clone(),
1048 Worktree::Remote(worktree) => Some((worktree.rpc.clone(), worktree.remote_id)),
1049 } {
1050 cx.background()
1051 .spawn(async move {
1052 if let Err(error) = rpc
1053 .send(proto::UpdateBuffer {
1054 worktree_id: remote_id,
1055 buffer_id,
1056 operations: Some(operation).iter().map(Into::into).collect(),
1057 })
1058 .await
1059 {
1060 log::error!("error sending buffer operation: {}", error);
1061 }
1062 })
1063 .detach();
1064 }
1065 });
1066 }
1067
1068 pub fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext) {
1069 self.worktree.update(cx, |worktree, cx| {
1070 if let Worktree::Remote(worktree) = worktree {
1071 let worktree_id = worktree.remote_id;
1072 let rpc = worktree.rpc.clone();
1073 cx.background()
1074 .spawn(async move {
1075 if let Err(error) = rpc
1076 .send(proto::CloseBuffer {
1077 worktree_id,
1078 buffer_id,
1079 })
1080 .await
1081 {
1082 log::error!("error closing remote buffer: {}", error);
1083 };
1084 })
1085 .detach();
1086 }
1087 });
1088 }
1089
1090 /// Returns this file's path relative to the root of its worktree.
1091 pub fn path(&self) -> Arc<Path> {
1092 self.path.clone()
1093 }
1094
1095 pub fn abs_path(&self, cx: &AppContext) -> PathBuf {
1096 self.worktree.read(cx).abs_path.join(&self.path)
1097 }
1098
1099 /// Returns the last component of this handle's absolute path. If this handle refers to the root
1100 /// of its worktree, then this method will return the name of the worktree itself.
1101 pub fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option<OsString> {
1102 self.path
1103 .file_name()
1104 .or_else(|| Some(OsStr::new(self.worktree.read(cx).root_name())))
1105 .map(Into::into)
1106 }
1107
1108 pub fn is_deleted(&self) -> bool {
1109 self.entry_id.is_none()
1110 }
1111
1112 pub fn exists(&self) -> bool {
1113 !self.is_deleted()
1114 }
1115
1116 pub fn save(&self, text: Rope, cx: &mut MutableAppContext) -> impl Future<Output = Result<()>> {
1117 self.worktree
1118 .update(cx, |worktree, cx| worktree.save(&self.path, text, cx))
1119 }
1120
1121 pub fn worktree_id(&self) -> usize {
1122 self.worktree.id()
1123 }
1124
1125 pub fn entry_id(&self) -> (usize, Arc<Path>) {
1126 (self.worktree.id(), self.path())
1127 }
1128}
1129
1130#[derive(Clone, Debug)]
1131pub struct Entry {
1132 id: usize,
1133 kind: EntryKind,
1134 path: Arc<Path>,
1135 inode: u64,
1136 mtime: SystemTime,
1137 is_symlink: bool,
1138 is_ignored: bool,
1139}
1140
1141#[derive(Clone, Debug)]
1142pub enum EntryKind {
1143 PendingDir,
1144 Dir,
1145 File(CharBag),
1146}
1147
1148impl Entry {
1149 pub fn path(&self) -> &Arc<Path> {
1150 &self.path
1151 }
1152
1153 pub fn inode(&self) -> u64 {
1154 self.inode
1155 }
1156
1157 pub fn is_ignored(&self) -> bool {
1158 self.is_ignored
1159 }
1160
1161 fn is_dir(&self) -> bool {
1162 matches!(self.kind, EntryKind::Dir | EntryKind::PendingDir)
1163 }
1164
1165 fn is_file(&self) -> bool {
1166 matches!(self.kind, EntryKind::File(_))
1167 }
1168}
1169
1170impl sum_tree::Item for Entry {
1171 type Summary = EntrySummary;
1172
1173 fn summary(&self) -> Self::Summary {
1174 let file_count;
1175 let visible_file_count;
1176 if self.is_file() {
1177 file_count = 1;
1178 if self.is_ignored {
1179 visible_file_count = 0;
1180 } else {
1181 visible_file_count = 1;
1182 }
1183 } else {
1184 file_count = 0;
1185 visible_file_count = 0;
1186 }
1187
1188 EntrySummary {
1189 max_path: self.path().clone(),
1190 file_count,
1191 visible_file_count,
1192 }
1193 }
1194}
1195
1196impl sum_tree::KeyedItem for Entry {
1197 type Key = PathKey;
1198
1199 fn key(&self) -> Self::Key {
1200 PathKey(self.path().clone())
1201 }
1202}
1203
1204#[derive(Clone, Debug)]
1205pub struct EntrySummary {
1206 max_path: Arc<Path>,
1207 file_count: usize,
1208 visible_file_count: usize,
1209}
1210
1211impl Default for EntrySummary {
1212 fn default() -> Self {
1213 Self {
1214 max_path: Arc::from(Path::new("")),
1215 file_count: 0,
1216 visible_file_count: 0,
1217 }
1218 }
1219}
1220
1221impl sum_tree::Summary for EntrySummary {
1222 type Context = ();
1223
1224 fn add_summary(&mut self, rhs: &Self, _: &()) {
1225 self.max_path = rhs.max_path.clone();
1226 self.file_count += rhs.file_count;
1227 self.visible_file_count += rhs.visible_file_count;
1228 }
1229}
1230
1231#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
1232pub struct PathKey(Arc<Path>);
1233
1234impl Default for PathKey {
1235 fn default() -> Self {
1236 Self(Path::new("").into())
1237 }
1238}
1239
1240impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
1241 fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
1242 self.0 = summary.max_path.clone();
1243 }
1244}
1245
1246#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1247enum PathSearch<'a> {
1248 Exact(&'a Path),
1249 Successor(&'a Path),
1250}
1251
1252impl<'a> Ord for PathSearch<'a> {
1253 fn cmp(&self, other: &Self) -> cmp::Ordering {
1254 match (self, other) {
1255 (Self::Exact(a), Self::Exact(b)) => a.cmp(b),
1256 (Self::Successor(a), Self::Exact(b)) => {
1257 if b.starts_with(a) {
1258 cmp::Ordering::Greater
1259 } else {
1260 a.cmp(b)
1261 }
1262 }
1263 _ => unreachable!("not sure we need the other two cases"),
1264 }
1265 }
1266}
1267
1268impl<'a> PartialOrd for PathSearch<'a> {
1269 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
1270 Some(self.cmp(other))
1271 }
1272}
1273
1274impl<'a> Default for PathSearch<'a> {
1275 fn default() -> Self {
1276 Self::Exact(Path::new("").into())
1277 }
1278}
1279
1280impl<'a: 'b, 'b> sum_tree::Dimension<'a, EntrySummary> for PathSearch<'b> {
1281 fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
1282 *self = Self::Exact(summary.max_path.as_ref());
1283 }
1284}
1285
1286#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
1287pub struct FileCount(usize);
1288
1289impl<'a> sum_tree::Dimension<'a, EntrySummary> for FileCount {
1290 fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
1291 self.0 += summary.file_count;
1292 }
1293}
1294
1295#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
1296pub struct VisibleFileCount(usize);
1297
1298impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleFileCount {
1299 fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
1300 self.0 += summary.visible_file_count;
1301 }
1302}
1303
1304struct BackgroundScanner {
1305 snapshot: Arc<Mutex<Snapshot>>,
1306 notify: Sender<ScanState>,
1307 thread_pool: scoped_pool::Pool,
1308}
1309
1310impl BackgroundScanner {
1311 fn new(snapshot: Arc<Mutex<Snapshot>>, notify: Sender<ScanState>, worktree_id: usize) -> Self {
1312 Self {
1313 snapshot,
1314 notify,
1315 thread_pool: scoped_pool::Pool::new(16, format!("worktree-{}-scanner", worktree_id)),
1316 }
1317 }
1318
1319 fn abs_path(&self) -> Arc<Path> {
1320 self.snapshot.lock().abs_path.clone()
1321 }
1322
1323 fn snapshot(&self) -> Snapshot {
1324 self.snapshot.lock().clone()
1325 }
1326
1327 fn run(mut self, event_stream: fsevent::EventStream) {
1328 if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
1329 return;
1330 }
1331
1332 if let Err(err) = self.scan_dirs() {
1333 if smol::block_on(self.notify.send(ScanState::Err(Arc::new(err)))).is_err() {
1334 return;
1335 }
1336 }
1337
1338 if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
1339 return;
1340 }
1341
1342 event_stream.run(move |events| {
1343 if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
1344 return false;
1345 }
1346
1347 if !self.process_events(events) {
1348 return false;
1349 }
1350
1351 if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
1352 return false;
1353 }
1354
1355 true
1356 });
1357 }
1358
1359 fn scan_dirs(&mut self) -> io::Result<()> {
1360 self.snapshot.lock().scan_id += 1;
1361
1362 let path: Arc<Path> = Arc::from(Path::new(""));
1363 let abs_path = self.abs_path();
1364 let metadata = fs::metadata(&abs_path)?;
1365 let inode = metadata.ino();
1366 let is_symlink = fs::symlink_metadata(&abs_path)?.file_type().is_symlink();
1367 let is_dir = metadata.file_type().is_dir();
1368 let mtime = metadata.modified()?;
1369
1370 // After determining whether the root entry is a file or a directory, populate the
1371 // snapshot's "root name", which will be used for the purpose of fuzzy matching.
1372 let mut root_name = abs_path
1373 .file_name()
1374 .map_or(String::new(), |f| f.to_string_lossy().to_string());
1375 if is_dir {
1376 root_name.push('/');
1377 }
1378
1379 let root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
1380 let next_entry_id;
1381 {
1382 let mut snapshot = self.snapshot.lock();
1383 snapshot.root_name = root_name;
1384 snapshot.root_char_bag = root_char_bag;
1385 next_entry_id = snapshot.next_entry_id.clone();
1386 }
1387
1388 if is_dir {
1389 self.snapshot.lock().insert_entry(Entry {
1390 id: next_entry_id.fetch_add(1, SeqCst),
1391 kind: EntryKind::PendingDir,
1392 path: path.clone(),
1393 inode,
1394 mtime,
1395 is_symlink,
1396 is_ignored: false,
1397 });
1398
1399 let (tx, rx) = crossbeam_channel::unbounded();
1400 tx.send(ScanJob {
1401 abs_path: abs_path.to_path_buf(),
1402 path,
1403 ignore_stack: IgnoreStack::none(),
1404 scan_queue: tx.clone(),
1405 })
1406 .unwrap();
1407 drop(tx);
1408
1409 self.thread_pool.scoped(|pool| {
1410 for _ in 0..self.thread_pool.thread_count() {
1411 pool.execute(|| {
1412 while let Ok(job) = rx.recv() {
1413 if let Err(err) =
1414 self.scan_dir(root_char_bag, next_entry_id.clone(), &job)
1415 {
1416 log::error!("error scanning {:?}: {}", job.abs_path, err);
1417 }
1418 }
1419 });
1420 }
1421 });
1422 } else {
1423 self.snapshot.lock().insert_entry(Entry {
1424 id: next_entry_id.fetch_add(1, SeqCst),
1425 kind: EntryKind::File(char_bag_for_path(root_char_bag, &path)),
1426 path,
1427 inode,
1428 mtime,
1429 is_symlink,
1430 is_ignored: false,
1431 });
1432 }
1433
1434 Ok(())
1435 }
1436
1437 fn scan_dir(
1438 &self,
1439 root_char_bag: CharBag,
1440 next_entry_id: Arc<AtomicUsize>,
1441 job: &ScanJob,
1442 ) -> io::Result<()> {
1443 let mut new_entries: Vec<Entry> = Vec::new();
1444 let mut new_jobs: Vec<ScanJob> = Vec::new();
1445 let mut ignore_stack = job.ignore_stack.clone();
1446 let mut new_ignore = None;
1447
1448 for child_entry in fs::read_dir(&job.abs_path)? {
1449 let child_entry = child_entry?;
1450 let child_name = child_entry.file_name();
1451 let child_abs_path = job.abs_path.join(&child_name);
1452 let child_path: Arc<Path> = job.path.join(&child_name).into();
1453 let child_is_symlink = child_entry.metadata()?.file_type().is_symlink();
1454 let child_metadata = if let Ok(metadata) = fs::metadata(&child_abs_path) {
1455 metadata
1456 } else {
1457 log::error!("could not get metadata for path {:?}", child_abs_path);
1458 continue;
1459 };
1460
1461 let child_inode = child_metadata.ino();
1462 let child_mtime = child_metadata.modified()?;
1463
1464 // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored
1465 if child_name == *GITIGNORE {
1466 let (ignore, err) = Gitignore::new(&child_abs_path);
1467 if let Some(err) = err {
1468 log::error!("error in ignore file {:?} - {:?}", child_path, err);
1469 }
1470 let ignore = Arc::new(ignore);
1471 ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone());
1472 new_ignore = Some(ignore);
1473
1474 // Update ignore status of any child entries we've already processed to reflect the
1475 // ignore file in the current directory. Because `.gitignore` starts with a `.`,
1476 // there should rarely be too numerous. Update the ignore stack associated with any
1477 // new jobs as well.
1478 let mut new_jobs = new_jobs.iter_mut();
1479 for entry in &mut new_entries {
1480 entry.is_ignored = ignore_stack.is_path_ignored(&entry.path, entry.is_dir());
1481 if entry.is_dir() {
1482 new_jobs.next().unwrap().ignore_stack = if entry.is_ignored {
1483 IgnoreStack::all()
1484 } else {
1485 ignore_stack.clone()
1486 };
1487 }
1488 }
1489 }
1490
1491 if child_metadata.is_dir() {
1492 let is_ignored = ignore_stack.is_path_ignored(&child_path, true);
1493 new_entries.push(Entry {
1494 id: next_entry_id.fetch_add(1, SeqCst),
1495 kind: EntryKind::PendingDir,
1496 path: child_path.clone(),
1497 inode: child_inode,
1498 mtime: child_mtime,
1499 is_symlink: child_is_symlink,
1500 is_ignored,
1501 });
1502 new_jobs.push(ScanJob {
1503 abs_path: child_abs_path,
1504 path: child_path,
1505 ignore_stack: if is_ignored {
1506 IgnoreStack::all()
1507 } else {
1508 ignore_stack.clone()
1509 },
1510 scan_queue: job.scan_queue.clone(),
1511 });
1512 } else {
1513 let is_ignored = ignore_stack.is_path_ignored(&child_path, false);
1514 new_entries.push(Entry {
1515 id: next_entry_id.fetch_add(1, SeqCst),
1516 kind: EntryKind::File(char_bag_for_path(root_char_bag, &child_path)),
1517 path: child_path,
1518 inode: child_inode,
1519 mtime: child_mtime,
1520 is_symlink: child_is_symlink,
1521 is_ignored,
1522 });
1523 };
1524 }
1525
1526 self.snapshot
1527 .lock()
1528 .populate_dir(job.path.clone(), new_entries, new_ignore);
1529 for new_job in new_jobs {
1530 job.scan_queue.send(new_job).unwrap();
1531 }
1532
1533 Ok(())
1534 }
1535
1536 fn process_events(&mut self, mut events: Vec<fsevent::Event>) -> bool {
1537 let mut snapshot = self.snapshot();
1538 snapshot.scan_id += 1;
1539
1540 let root_abs_path = if let Ok(abs_path) = snapshot.abs_path.canonicalize() {
1541 abs_path
1542 } else {
1543 return false;
1544 };
1545 let root_char_bag = snapshot.root_char_bag;
1546 let next_entry_id = snapshot.next_entry_id.clone();
1547
1548 events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
1549 events.dedup_by(|a, b| a.path.starts_with(&b.path));
1550
1551 for event in &events {
1552 match event.path.strip_prefix(&root_abs_path) {
1553 Ok(path) => snapshot.remove_path(&path),
1554 Err(_) => {
1555 log::error!(
1556 "unexpected event {:?} for root path {:?}",
1557 event.path,
1558 root_abs_path
1559 );
1560 continue;
1561 }
1562 }
1563 }
1564
1565 let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded();
1566 for event in events {
1567 let path: Arc<Path> = match event.path.strip_prefix(&root_abs_path) {
1568 Ok(path) => Arc::from(path.to_path_buf()),
1569 Err(_) => {
1570 log::error!(
1571 "unexpected event {:?} for root path {:?}",
1572 event.path,
1573 root_abs_path
1574 );
1575 continue;
1576 }
1577 };
1578
1579 match fs_entry_for_path(
1580 snapshot.root_char_bag,
1581 &next_entry_id,
1582 path.clone(),
1583 &event.path,
1584 ) {
1585 Ok(Some(mut fs_entry)) => {
1586 let is_dir = fs_entry.is_dir();
1587 let ignore_stack = snapshot.ignore_stack_for_path(&path, is_dir);
1588 fs_entry.is_ignored = ignore_stack.is_all();
1589 snapshot.insert_entry(fs_entry);
1590 if is_dir {
1591 scan_queue_tx
1592 .send(ScanJob {
1593 abs_path: event.path,
1594 path,
1595 ignore_stack,
1596 scan_queue: scan_queue_tx.clone(),
1597 })
1598 .unwrap();
1599 }
1600 }
1601 Ok(None) => {}
1602 Err(err) => {
1603 // TODO - create a special 'error' entry in the entries tree to mark this
1604 log::error!("error reading file on event {:?}", err);
1605 }
1606 }
1607 }
1608
1609 *self.snapshot.lock() = snapshot;
1610
1611 // Scan any directories that were created as part of this event batch.
1612 drop(scan_queue_tx);
1613 self.thread_pool.scoped(|pool| {
1614 for _ in 0..self.thread_pool.thread_count() {
1615 pool.execute(|| {
1616 while let Ok(job) = scan_queue_rx.recv() {
1617 if let Err(err) = self.scan_dir(root_char_bag, next_entry_id.clone(), &job)
1618 {
1619 log::error!("error scanning {:?}: {}", job.abs_path, err);
1620 }
1621 }
1622 });
1623 }
1624 });
1625
1626 // Attempt to detect renames only over a single batch of file-system events.
1627 self.snapshot.lock().removed_entry_ids.clear();
1628
1629 self.update_ignore_statuses();
1630 true
1631 }
1632
1633 fn update_ignore_statuses(&self) {
1634 let mut snapshot = self.snapshot();
1635
1636 let mut ignores_to_update = Vec::new();
1637 let mut ignores_to_delete = Vec::new();
1638 for (parent_path, (_, scan_id)) in &snapshot.ignores {
1639 if *scan_id == snapshot.scan_id && snapshot.entry_for_path(parent_path).is_some() {
1640 ignores_to_update.push(parent_path.clone());
1641 }
1642
1643 let ignore_path = parent_path.join(&*GITIGNORE);
1644 if snapshot.entry_for_path(ignore_path).is_none() {
1645 ignores_to_delete.push(parent_path.clone());
1646 }
1647 }
1648
1649 for parent_path in ignores_to_delete {
1650 snapshot.ignores.remove(&parent_path);
1651 self.snapshot.lock().ignores.remove(&parent_path);
1652 }
1653
1654 let (ignore_queue_tx, ignore_queue_rx) = crossbeam_channel::unbounded();
1655 ignores_to_update.sort_unstable();
1656 let mut ignores_to_update = ignores_to_update.into_iter().peekable();
1657 while let Some(parent_path) = ignores_to_update.next() {
1658 while ignores_to_update
1659 .peek()
1660 .map_or(false, |p| p.starts_with(&parent_path))
1661 {
1662 ignores_to_update.next().unwrap();
1663 }
1664
1665 let ignore_stack = snapshot.ignore_stack_for_path(&parent_path, true);
1666 ignore_queue_tx
1667 .send(UpdateIgnoreStatusJob {
1668 path: parent_path,
1669 ignore_stack,
1670 ignore_queue: ignore_queue_tx.clone(),
1671 })
1672 .unwrap();
1673 }
1674 drop(ignore_queue_tx);
1675
1676 self.thread_pool.scoped(|scope| {
1677 for _ in 0..self.thread_pool.thread_count() {
1678 scope.execute(|| {
1679 while let Ok(job) = ignore_queue_rx.recv() {
1680 self.update_ignore_status(job, &snapshot);
1681 }
1682 });
1683 }
1684 });
1685 }
1686
1687 fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &Snapshot) {
1688 let mut ignore_stack = job.ignore_stack;
1689 if let Some((ignore, _)) = snapshot.ignores.get(&job.path) {
1690 ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone());
1691 }
1692
1693 let mut edits = Vec::new();
1694 for mut entry in snapshot.child_entries(&job.path).cloned() {
1695 let was_ignored = entry.is_ignored;
1696 entry.is_ignored = ignore_stack.is_path_ignored(entry.path(), entry.is_dir());
1697 if entry.is_dir() {
1698 let child_ignore_stack = if entry.is_ignored {
1699 IgnoreStack::all()
1700 } else {
1701 ignore_stack.clone()
1702 };
1703 job.ignore_queue
1704 .send(UpdateIgnoreStatusJob {
1705 path: entry.path().clone(),
1706 ignore_stack: child_ignore_stack,
1707 ignore_queue: job.ignore_queue.clone(),
1708 })
1709 .unwrap();
1710 }
1711
1712 if entry.is_ignored != was_ignored {
1713 edits.push(Edit::Insert(entry));
1714 }
1715 }
1716 self.snapshot.lock().entries.edit(edits, &());
1717 }
1718}
1719
1720fn refresh_entry(snapshot: &Mutex<Snapshot>, path: Arc<Path>, abs_path: &Path) -> Result<Entry> {
1721 let root_char_bag;
1722 let next_entry_id;
1723 {
1724 let snapshot = snapshot.lock();
1725 root_char_bag = snapshot.root_char_bag;
1726 next_entry_id = snapshot.next_entry_id.clone();
1727 }
1728 let entry = fs_entry_for_path(root_char_bag, &next_entry_id, path, abs_path)?
1729 .ok_or_else(|| anyhow!("could not read saved file metadata"))?;
1730 Ok(snapshot.lock().insert_entry(entry))
1731}
1732
1733fn fs_entry_for_path(
1734 root_char_bag: CharBag,
1735 next_entry_id: &AtomicUsize,
1736 path: Arc<Path>,
1737 abs_path: &Path,
1738) -> Result<Option<Entry>> {
1739 let metadata = match fs::metadata(&abs_path) {
1740 Err(err) => {
1741 return match (err.kind(), err.raw_os_error()) {
1742 (io::ErrorKind::NotFound, _) => Ok(None),
1743 (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
1744 _ => Err(anyhow::Error::new(err)),
1745 }
1746 }
1747 Ok(metadata) => metadata,
1748 };
1749 let inode = metadata.ino();
1750 let mtime = metadata.modified()?;
1751 let is_symlink = fs::symlink_metadata(&abs_path)
1752 .context("failed to read symlink metadata")?
1753 .file_type()
1754 .is_symlink();
1755
1756 let entry = Entry {
1757 id: next_entry_id.fetch_add(1, SeqCst),
1758 kind: if metadata.file_type().is_dir() {
1759 EntryKind::PendingDir
1760 } else {
1761 EntryKind::File(char_bag_for_path(root_char_bag, &path))
1762 },
1763 path: Arc::from(path),
1764 inode,
1765 mtime,
1766 is_symlink,
1767 is_ignored: false,
1768 };
1769
1770 Ok(Some(entry))
1771}
1772
1773fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
1774 let mut result = root_char_bag;
1775 result.extend(
1776 path.to_string_lossy()
1777 .chars()
1778 .map(|c| c.to_ascii_lowercase()),
1779 );
1780 result
1781}
1782
1783struct ScanJob {
1784 abs_path: PathBuf,
1785 path: Arc<Path>,
1786 ignore_stack: Arc<IgnoreStack>,
1787 scan_queue: crossbeam_channel::Sender<ScanJob>,
1788}
1789
1790struct UpdateIgnoreStatusJob {
1791 path: Arc<Path>,
1792 ignore_stack: Arc<IgnoreStack>,
1793 ignore_queue: crossbeam_channel::Sender<UpdateIgnoreStatusJob>,
1794}
1795
1796pub trait WorktreeHandle {
1797 #[cfg(test)]
1798 fn flush_fs_events<'a>(
1799 &self,
1800 cx: &'a gpui::TestAppContext,
1801 ) -> futures::future::LocalBoxFuture<'a, ()>;
1802}
1803
1804impl WorktreeHandle for ModelHandle<Worktree> {
1805 // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
1806 // occurred before the worktree was constructed. These events can cause the worktree to perfrom
1807 // extra directory scans, and emit extra scan-state notifications.
1808 //
1809 // This function mutates the worktree's directory and waits for those mutations to be picked up,
1810 // to ensure that all redundant FS events have already been processed.
1811 #[cfg(test)]
1812 fn flush_fs_events<'a>(
1813 &self,
1814 cx: &'a gpui::TestAppContext,
1815 ) -> futures::future::LocalBoxFuture<'a, ()> {
1816 use smol::future::FutureExt;
1817
1818 let filename = "fs-event-sentinel";
1819 let root_path = cx.read(|cx| self.read(cx).abs_path.clone());
1820 let tree = self.clone();
1821 async move {
1822 fs::write(root_path.join(filename), "").unwrap();
1823 tree.condition(&cx, |tree, _| tree.entry_for_path(filename).is_some())
1824 .await;
1825
1826 fs::remove_file(root_path.join(filename)).unwrap();
1827 tree.condition(&cx, |tree, _| tree.entry_for_path(filename).is_none())
1828 .await;
1829
1830 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1831 .await;
1832 }
1833 .boxed_local()
1834 }
1835}
1836
1837pub enum FileIter<'a> {
1838 All(Cursor<'a, Entry, FileCount, ()>),
1839 Visible(Cursor<'a, Entry, VisibleFileCount, ()>),
1840}
1841
1842impl<'a> FileIter<'a> {
1843 fn all(snapshot: &'a Snapshot, start: usize) -> Self {
1844 let mut cursor = snapshot.entries.cursor();
1845 cursor.seek(&FileCount(start), Bias::Right, &());
1846 Self::All(cursor)
1847 }
1848
1849 fn visible(snapshot: &'a Snapshot, start: usize) -> Self {
1850 let mut cursor = snapshot.entries.cursor();
1851 cursor.seek(&VisibleFileCount(start), Bias::Right, &());
1852 Self::Visible(cursor)
1853 }
1854
1855 fn next_internal(&mut self) {
1856 match self {
1857 Self::All(cursor) => {
1858 let ix = *cursor.seek_start();
1859 cursor.seek_forward(&FileCount(ix.0 + 1), Bias::Right, &());
1860 }
1861 Self::Visible(cursor) => {
1862 let ix = *cursor.seek_start();
1863 cursor.seek_forward(&VisibleFileCount(ix.0 + 1), Bias::Right, &());
1864 }
1865 }
1866 }
1867
1868 fn item(&self) -> Option<&'a Entry> {
1869 match self {
1870 Self::All(cursor) => cursor.item(),
1871 Self::Visible(cursor) => cursor.item(),
1872 }
1873 }
1874}
1875
1876impl<'a> Iterator for FileIter<'a> {
1877 type Item = &'a Entry;
1878
1879 fn next(&mut self) -> Option<Self::Item> {
1880 if let Some(entry) = self.item() {
1881 self.next_internal();
1882 Some(entry)
1883 } else {
1884 None
1885 }
1886 }
1887}
1888
1889struct ChildEntriesIter<'a> {
1890 parent_path: &'a Path,
1891 cursor: Cursor<'a, Entry, PathSearch<'a>, ()>,
1892}
1893
1894impl<'a> ChildEntriesIter<'a> {
1895 fn new(parent_path: &'a Path, snapshot: &'a Snapshot) -> Self {
1896 let mut cursor = snapshot.entries.cursor();
1897 cursor.seek(&PathSearch::Exact(parent_path), Bias::Right, &());
1898 Self {
1899 parent_path,
1900 cursor,
1901 }
1902 }
1903}
1904
1905impl<'a> Iterator for ChildEntriesIter<'a> {
1906 type Item = &'a Entry;
1907
1908 fn next(&mut self) -> Option<Self::Item> {
1909 if let Some(item) = self.cursor.item() {
1910 if item.path().starts_with(self.parent_path) {
1911 self.cursor
1912 .seek_forward(&PathSearch::Successor(item.path()), Bias::Left, &());
1913 Some(item)
1914 } else {
1915 None
1916 }
1917 } else {
1918 None
1919 }
1920 }
1921}
1922
1923mod remote {
1924 use super::*;
1925 use crate::rpc::TypedEnvelope;
1926 use std::convert::TryInto;
1927
1928 pub async fn open_buffer(
1929 envelope: TypedEnvelope<proto::OpenBuffer>,
1930 rpc: &rpc::Client,
1931 cx: &mut AsyncAppContext,
1932 ) -> anyhow::Result<()> {
1933 let receipt = envelope.receipt();
1934 let worktree = rpc
1935 .state
1936 .lock()
1937 .await
1938 .shared_worktree(envelope.payload.worktree_id, cx)?;
1939
1940 let response = worktree
1941 .update(cx, |worktree, cx| {
1942 worktree
1943 .as_local_mut()
1944 .unwrap()
1945 .open_remote_buffer(envelope, cx)
1946 })
1947 .await?;
1948
1949 rpc.respond(receipt, response).await?;
1950
1951 Ok(())
1952 }
1953
1954 pub async fn close_buffer(
1955 envelope: TypedEnvelope<proto::CloseBuffer>,
1956 rpc: &rpc::Client,
1957 cx: &mut AsyncAppContext,
1958 ) -> anyhow::Result<()> {
1959 let worktree = rpc
1960 .state
1961 .lock()
1962 .await
1963 .shared_worktree(envelope.payload.worktree_id, cx)?;
1964
1965 worktree.update(cx, |worktree, cx| {
1966 worktree
1967 .as_local_mut()
1968 .unwrap()
1969 .close_remote_buffer(envelope, cx)
1970 })
1971 }
1972
1973 pub async fn update_buffer(
1974 envelope: TypedEnvelope<proto::UpdateBuffer>,
1975 rpc: &rpc::Client,
1976 cx: &mut AsyncAppContext,
1977 ) -> anyhow::Result<()> {
1978 let message = envelope.payload;
1979 let mut state = rpc.state.lock().await;
1980 match state.shared_worktree(message.worktree_id, cx) {
1981 Ok(worktree) => {
1982 if let Some(buffer) =
1983 worktree.read_with(cx, |w, cx| w.buffer(message.buffer_id, cx))
1984 {
1985 if let Err(error) = buffer.update(cx, |buffer, cx| {
1986 let ops = message
1987 .operations
1988 .into_iter()
1989 .map(|op| op.try_into())
1990 .collect::<anyhow::Result<Vec<_>>>()?;
1991 buffer.apply_ops(ops, cx)?;
1992 Ok::<(), anyhow::Error>(())
1993 }) {
1994 log::error!("error applying buffer operations {}", error);
1995 }
1996 } else {
1997 log::error!(
1998 "invalid buffer {} in update buffer message",
1999 message.buffer_id
2000 );
2001 }
2002 }
2003 Err(error) => log::error!("{}", error),
2004 }
2005
2006 Ok(())
2007 }
2008
2009 pub async fn remove_guest(
2010 envelope: TypedEnvelope<proto::RemoveGuest>,
2011 rpc: &rpc::Client,
2012 cx: &mut AsyncAppContext,
2013 ) -> anyhow::Result<()> {
2014 rpc.state
2015 .lock()
2016 .await
2017 .shared_worktree(envelope.payload.worktree_id, cx)?
2018 .update(cx, |worktree, cx| match worktree {
2019 Worktree::Local(worktree) => worktree.remove_guest(envelope, cx),
2020 Worktree::Remote(_) => todo!(),
2021 })
2022 }
2023}
2024
2025#[cfg(test)]
2026mod tests {
2027 use super::*;
2028 use crate::test::*;
2029 use anyhow::Result;
2030 use rand::prelude::*;
2031 use serde_json::json;
2032 use std::time::UNIX_EPOCH;
2033 use std::{env, fmt::Write, os::unix, time::SystemTime};
2034
2035 #[gpui::test]
2036 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
2037 let dir = temp_tree(json!({
2038 "root": {
2039 "apple": "",
2040 "banana": {
2041 "carrot": {
2042 "date": "",
2043 "endive": "",
2044 }
2045 },
2046 "fennel": {
2047 "grape": "",
2048 }
2049 }
2050 }));
2051
2052 let root_link_path = dir.path().join("root_link");
2053 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
2054 unix::fs::symlink(
2055 &dir.path().join("root/fennel"),
2056 &dir.path().join("root/finnochio"),
2057 )
2058 .unwrap();
2059
2060 let tree = cx.add_model(|cx| Worktree::local(root_link_path, Default::default(), cx));
2061
2062 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2063 .await;
2064 cx.read(|cx| {
2065 let tree = tree.read(cx);
2066 assert_eq!(tree.file_count(), 5);
2067
2068 assert_eq!(
2069 tree.inode_for_path("fennel/grape"),
2070 tree.inode_for_path("finnochio/grape")
2071 );
2072
2073 let results = match_paths(
2074 Some(tree.snapshot()).iter(),
2075 "bna",
2076 false,
2077 false,
2078 false,
2079 10,
2080 Default::default(),
2081 cx.thread_pool().clone(),
2082 )
2083 .into_iter()
2084 .map(|result| result.path)
2085 .collect::<Vec<Arc<Path>>>();
2086 assert_eq!(
2087 results,
2088 vec![
2089 PathBuf::from("banana/carrot/date").into(),
2090 PathBuf::from("banana/carrot/endive").into(),
2091 ]
2092 );
2093 })
2094 }
2095
2096 #[gpui::test]
2097 async fn test_save_file(mut cx: gpui::TestAppContext) {
2098 let app_state = cx.read(build_app_state);
2099 let dir = temp_tree(json!({
2100 "file1": "the old contents",
2101 }));
2102 let tree = cx.add_model(|cx| Worktree::local(dir.path(), app_state.languages, cx));
2103 let buffer = tree
2104 .update(&mut cx, |tree, cx| tree.open_buffer("file1", cx))
2105 .await
2106 .unwrap();
2107 let save = buffer.update(&mut cx, |buffer, cx| {
2108 buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
2109 buffer.save(cx).unwrap()
2110 });
2111 save.await.unwrap();
2112
2113 let new_text = fs::read_to_string(dir.path().join("file1")).unwrap();
2114 assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
2115 }
2116
2117 #[gpui::test]
2118 async fn test_save_in_single_file_worktree(mut cx: gpui::TestAppContext) {
2119 let app_state = cx.read(build_app_state);
2120 let dir = temp_tree(json!({
2121 "file1": "the old contents",
2122 }));
2123 let file_path = dir.path().join("file1");
2124
2125 let tree = cx.add_model(|cx| Worktree::local(file_path.clone(), app_state.languages, cx));
2126 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2127 .await;
2128 cx.read(|cx| assert_eq!(tree.read(cx).file_count(), 1));
2129
2130 let buffer = tree
2131 .update(&mut cx, |tree, cx| tree.open_buffer("", cx))
2132 .await
2133 .unwrap();
2134 let save = buffer.update(&mut cx, |buffer, cx| {
2135 buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
2136 buffer.save(cx).unwrap()
2137 });
2138 save.await.unwrap();
2139
2140 let new_text = fs::read_to_string(file_path).unwrap();
2141 assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
2142 }
2143
2144 #[gpui::test]
2145 async fn test_rescan_simple(mut cx: gpui::TestAppContext) {
2146 let dir = temp_tree(json!({
2147 "a": {
2148 "file1": "",
2149 "file2": "",
2150 "file3": "",
2151 },
2152 "b": {
2153 "c": {
2154 "file4": "",
2155 "file5": "",
2156 }
2157 }
2158 }));
2159
2160 let tree = cx.add_model(|cx| Worktree::local(dir.path(), Default::default(), cx));
2161
2162 let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
2163 let buffer = tree.update(cx, |tree, cx| tree.open_buffer(path, cx));
2164 async move { buffer.await.unwrap() }
2165 };
2166 let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
2167 tree.read_with(cx, |tree, _| {
2168 tree.entry_for_path(path)
2169 .expect(&format!("no entry for path {}", path))
2170 .id
2171 })
2172 };
2173
2174 let buffer2 = buffer_for_path("a/file2", &mut cx).await;
2175 let buffer3 = buffer_for_path("a/file3", &mut cx).await;
2176 let buffer4 = buffer_for_path("b/c/file4", &mut cx).await;
2177 let buffer5 = buffer_for_path("b/c/file5", &mut cx).await;
2178
2179 let file2_id = id_for_path("a/file2", &cx);
2180 let file3_id = id_for_path("a/file3", &cx);
2181 let file4_id = id_for_path("b/c/file4", &cx);
2182
2183 // After scanning, the worktree knows which files exist and which don't.
2184 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2185 .await;
2186
2187 cx.read(|cx| {
2188 assert!(!buffer2.read(cx).is_dirty());
2189 assert!(!buffer3.read(cx).is_dirty());
2190 assert!(!buffer4.read(cx).is_dirty());
2191 assert!(!buffer5.read(cx).is_dirty());
2192 });
2193
2194 tree.flush_fs_events(&cx).await;
2195 std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
2196 std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
2197 std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
2198 std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
2199 tree.flush_fs_events(&cx).await;
2200
2201 cx.read(|app| {
2202 assert_eq!(
2203 tree.read(app)
2204 .paths()
2205 .map(|p| p.to_str().unwrap())
2206 .collect::<Vec<_>>(),
2207 vec![
2208 "a",
2209 "a/file1",
2210 "a/file2.new",
2211 "b",
2212 "d",
2213 "d/file3",
2214 "d/file4"
2215 ]
2216 );
2217
2218 assert_eq!(id_for_path("a/file2.new", &cx), file2_id);
2219 assert_eq!(id_for_path("d/file3", &cx), file3_id);
2220 assert_eq!(id_for_path("d/file4", &cx), file4_id);
2221
2222 assert_eq!(
2223 buffer2.read(app).file().unwrap().path().as_ref(),
2224 Path::new("a/file2.new")
2225 );
2226 assert_eq!(
2227 buffer3.read(app).file().unwrap().path().as_ref(),
2228 Path::new("d/file3")
2229 );
2230 assert_eq!(
2231 buffer4.read(app).file().unwrap().path().as_ref(),
2232 Path::new("d/file4")
2233 );
2234 assert_eq!(
2235 buffer5.read(app).file().unwrap().path().as_ref(),
2236 Path::new("b/c/file5")
2237 );
2238
2239 assert!(!buffer2.read(app).file().unwrap().is_deleted());
2240 assert!(!buffer3.read(app).file().unwrap().is_deleted());
2241 assert!(!buffer4.read(app).file().unwrap().is_deleted());
2242 assert!(buffer5.read(app).file().unwrap().is_deleted());
2243 });
2244 }
2245
2246 #[gpui::test]
2247 async fn test_rescan_with_gitignore(mut cx: gpui::TestAppContext) {
2248 let dir = temp_tree(json!({
2249 ".git": {},
2250 ".gitignore": "ignored-dir\n",
2251 "tracked-dir": {
2252 "tracked-file1": "tracked contents",
2253 },
2254 "ignored-dir": {
2255 "ignored-file1": "ignored contents",
2256 }
2257 }));
2258
2259 let tree = cx.add_model(|cx| Worktree::local(dir.path(), Default::default(), cx));
2260 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2261 .await;
2262 tree.flush_fs_events(&cx).await;
2263 cx.read(|cx| {
2264 let tree = tree.read(cx);
2265 let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap();
2266 let ignored = tree.entry_for_path("ignored-dir/ignored-file1").unwrap();
2267 assert_eq!(tracked.is_ignored(), false);
2268 assert_eq!(ignored.is_ignored(), true);
2269 });
2270
2271 fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap();
2272 fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap();
2273 tree.flush_fs_events(&cx).await;
2274 cx.read(|cx| {
2275 let tree = tree.read(cx);
2276 let dot_git = tree.entry_for_path(".git").unwrap();
2277 let tracked = tree.entry_for_path("tracked-dir/tracked-file2").unwrap();
2278 let ignored = tree.entry_for_path("ignored-dir/ignored-file2").unwrap();
2279 assert_eq!(tracked.is_ignored(), false);
2280 assert_eq!(ignored.is_ignored(), true);
2281 assert_eq!(dot_git.is_ignored(), true);
2282 });
2283 }
2284
2285 #[test]
2286 fn test_random() {
2287 let iterations = env::var("ITERATIONS")
2288 .map(|i| i.parse().unwrap())
2289 .unwrap_or(100);
2290 let operations = env::var("OPERATIONS")
2291 .map(|o| o.parse().unwrap())
2292 .unwrap_or(40);
2293 let initial_entries = env::var("INITIAL_ENTRIES")
2294 .map(|o| o.parse().unwrap())
2295 .unwrap_or(20);
2296 let seeds = if let Ok(seed) = env::var("SEED").map(|s| s.parse().unwrap()) {
2297 seed..seed + 1
2298 } else {
2299 0..iterations
2300 };
2301
2302 for seed in seeds {
2303 dbg!(seed);
2304 let mut rng = StdRng::seed_from_u64(seed);
2305
2306 let root_dir = tempdir::TempDir::new(&format!("test-{}", seed)).unwrap();
2307 for _ in 0..initial_entries {
2308 randomly_mutate_tree(root_dir.path(), 1.0, &mut rng).unwrap();
2309 }
2310 log::info!("Generated initial tree");
2311
2312 let (notify_tx, _notify_rx) = smol::channel::unbounded();
2313 let mut scanner = BackgroundScanner::new(
2314 Arc::new(Mutex::new(Snapshot {
2315 id: 0,
2316 scan_id: 0,
2317 abs_path: root_dir.path().into(),
2318 entries: Default::default(),
2319 paths_by_id: Default::default(),
2320 removed_entry_ids: Default::default(),
2321 ignores: Default::default(),
2322 root_name: Default::default(),
2323 root_char_bag: Default::default(),
2324 next_entry_id: Default::default(),
2325 })),
2326 notify_tx,
2327 0,
2328 );
2329 scanner.scan_dirs().unwrap();
2330 scanner.snapshot().check_invariants();
2331
2332 let mut events = Vec::new();
2333 let mut mutations_len = operations;
2334 while mutations_len > 1 {
2335 if !events.is_empty() && rng.gen_bool(0.4) {
2336 let len = rng.gen_range(0..=events.len());
2337 let to_deliver = events.drain(0..len).collect::<Vec<_>>();
2338 log::info!("Delivering events: {:#?}", to_deliver);
2339 scanner.process_events(to_deliver);
2340 scanner.snapshot().check_invariants();
2341 } else {
2342 events.extend(randomly_mutate_tree(root_dir.path(), 0.6, &mut rng).unwrap());
2343 mutations_len -= 1;
2344 }
2345 }
2346 log::info!("Quiescing: {:#?}", events);
2347 scanner.process_events(events);
2348 scanner.snapshot().check_invariants();
2349
2350 let (notify_tx, _notify_rx) = smol::channel::unbounded();
2351 let mut new_scanner = BackgroundScanner::new(
2352 Arc::new(Mutex::new(Snapshot {
2353 id: 0,
2354 scan_id: 0,
2355 abs_path: root_dir.path().into(),
2356 entries: Default::default(),
2357 paths_by_id: Default::default(),
2358 removed_entry_ids: Default::default(),
2359 ignores: Default::default(),
2360 root_name: Default::default(),
2361 root_char_bag: Default::default(),
2362 next_entry_id: Default::default(),
2363 })),
2364 notify_tx,
2365 1,
2366 );
2367 new_scanner.scan_dirs().unwrap();
2368 assert_eq!(scanner.snapshot().to_vec(), new_scanner.snapshot().to_vec());
2369 }
2370 }
2371
2372 fn randomly_mutate_tree(
2373 root_path: &Path,
2374 insertion_probability: f64,
2375 rng: &mut impl Rng,
2376 ) -> Result<Vec<fsevent::Event>> {
2377 let root_path = root_path.canonicalize().unwrap();
2378 let (dirs, files) = read_dir_recursive(root_path.clone());
2379
2380 let mut events = Vec::new();
2381 let mut record_event = |path: PathBuf| {
2382 events.push(fsevent::Event {
2383 event_id: SystemTime::now()
2384 .duration_since(UNIX_EPOCH)
2385 .unwrap()
2386 .as_secs(),
2387 flags: fsevent::StreamFlags::empty(),
2388 path,
2389 });
2390 };
2391
2392 if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
2393 let path = dirs.choose(rng).unwrap();
2394 let new_path = path.join(gen_name(rng));
2395
2396 if rng.gen() {
2397 log::info!("Creating dir {:?}", new_path.strip_prefix(root_path)?);
2398 fs::create_dir(&new_path)?;
2399 } else {
2400 log::info!("Creating file {:?}", new_path.strip_prefix(root_path)?);
2401 fs::write(&new_path, "")?;
2402 }
2403 record_event(new_path);
2404 } else if rng.gen_bool(0.05) {
2405 let ignore_dir_path = dirs.choose(rng).unwrap();
2406 let ignore_path = ignore_dir_path.join(&*GITIGNORE);
2407
2408 let (subdirs, subfiles) = read_dir_recursive(ignore_dir_path.clone());
2409 let files_to_ignore = {
2410 let len = rng.gen_range(0..=subfiles.len());
2411 subfiles.choose_multiple(rng, len)
2412 };
2413 let dirs_to_ignore = {
2414 let len = rng.gen_range(0..subdirs.len());
2415 subdirs.choose_multiple(rng, len)
2416 };
2417
2418 let mut ignore_contents = String::new();
2419 for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
2420 write!(
2421 ignore_contents,
2422 "{}\n",
2423 path_to_ignore
2424 .strip_prefix(&ignore_dir_path)?
2425 .to_str()
2426 .unwrap()
2427 )
2428 .unwrap();
2429 }
2430 log::info!(
2431 "Creating {:?} with contents:\n{}",
2432 ignore_path.strip_prefix(&root_path)?,
2433 ignore_contents
2434 );
2435 fs::write(&ignore_path, ignore_contents).unwrap();
2436 record_event(ignore_path);
2437 } else {
2438 let old_path = {
2439 let file_path = files.choose(rng);
2440 let dir_path = dirs[1..].choose(rng);
2441 file_path.into_iter().chain(dir_path).choose(rng).unwrap()
2442 };
2443
2444 let is_rename = rng.gen();
2445 if is_rename {
2446 let new_path_parent = dirs
2447 .iter()
2448 .filter(|d| !d.starts_with(old_path))
2449 .choose(rng)
2450 .unwrap();
2451
2452 let overwrite_existing_dir =
2453 !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
2454 let new_path = if overwrite_existing_dir {
2455 fs::remove_dir_all(&new_path_parent).ok();
2456 new_path_parent.to_path_buf()
2457 } else {
2458 new_path_parent.join(gen_name(rng))
2459 };
2460
2461 log::info!(
2462 "Renaming {:?} to {}{:?}",
2463 old_path.strip_prefix(&root_path)?,
2464 if overwrite_existing_dir {
2465 "overwrite "
2466 } else {
2467 ""
2468 },
2469 new_path.strip_prefix(&root_path)?
2470 );
2471 fs::rename(&old_path, &new_path)?;
2472 record_event(old_path.clone());
2473 record_event(new_path);
2474 } else if old_path.is_dir() {
2475 let (dirs, files) = read_dir_recursive(old_path.clone());
2476
2477 log::info!("Deleting dir {:?}", old_path.strip_prefix(&root_path)?);
2478 fs::remove_dir_all(&old_path).unwrap();
2479 for file in files {
2480 record_event(file);
2481 }
2482 for dir in dirs {
2483 record_event(dir);
2484 }
2485 } else {
2486 log::info!("Deleting file {:?}", old_path.strip_prefix(&root_path)?);
2487 fs::remove_file(old_path).unwrap();
2488 record_event(old_path.clone());
2489 }
2490 }
2491
2492 Ok(events)
2493 }
2494
2495 fn read_dir_recursive(path: PathBuf) -> (Vec<PathBuf>, Vec<PathBuf>) {
2496 let child_entries = fs::read_dir(&path).unwrap();
2497 let mut dirs = vec![path];
2498 let mut files = Vec::new();
2499 for child_entry in child_entries {
2500 let child_path = child_entry.unwrap().path();
2501 if child_path.is_dir() {
2502 let (child_dirs, child_files) = read_dir_recursive(child_path);
2503 dirs.extend(child_dirs);
2504 files.extend(child_files);
2505 } else {
2506 files.push(child_path);
2507 }
2508 }
2509 (dirs, files)
2510 }
2511
2512 fn gen_name(rng: &mut impl Rng) -> String {
2513 (0..6)
2514 .map(|_| rng.sample(rand::distributions::Alphanumeric))
2515 .map(char::from)
2516 .collect()
2517 }
2518
2519 impl Snapshot {
2520 fn check_invariants(&self) {
2521 let mut files = self.files(0);
2522 let mut visible_files = self.visible_files(0);
2523 for entry in self.entries.cursor::<(), ()>() {
2524 if entry.is_file() {
2525 assert_eq!(files.next().unwrap().inode(), entry.inode);
2526 if !entry.is_ignored {
2527 assert_eq!(visible_files.next().unwrap().inode(), entry.inode);
2528 }
2529 }
2530 }
2531 assert!(files.next().is_none());
2532 assert!(visible_files.next().is_none());
2533
2534 let mut bfs_paths = Vec::new();
2535 let mut stack = vec![Path::new("")];
2536 while let Some(path) = stack.pop() {
2537 bfs_paths.push(path);
2538 let ix = stack.len();
2539 for child_entry in self.child_entries(path) {
2540 stack.insert(ix, child_entry.path());
2541 }
2542 }
2543
2544 let dfs_paths = self
2545 .entries
2546 .cursor::<(), ()>()
2547 .map(|e| e.path().as_ref())
2548 .collect::<Vec<_>>();
2549 assert_eq!(bfs_paths, dfs_paths);
2550
2551 for (ignore_parent_path, _) in &self.ignores {
2552 assert!(self.entry_for_path(ignore_parent_path).is_some());
2553 assert!(self
2554 .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
2555 .is_some());
2556 }
2557 }
2558
2559 fn to_vec(&self) -> Vec<(&Path, u64, bool)> {
2560 let mut paths = Vec::new();
2561 for entry in self.entries.cursor::<(), ()>() {
2562 paths.push((entry.path().as_ref(), entry.inode(), entry.is_ignored()));
2563 }
2564 paths.sort_by(|a, b| a.0.cmp(&b.0));
2565 paths
2566 }
2567 }
2568}