Port to gpui2
This commit is contained in:
parent
e5616bce98
commit
16b5d4b35c
17 changed files with 2585 additions and 355 deletions
|
@ -1151,20 +1151,22 @@ impl Project {
|
|||
project_path: impl Into<ProjectPath>,
|
||||
is_directory: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<Task<Result<Entry>>> {
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
let project_path = project_path.into();
|
||||
let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
|
||||
let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
|
||||
return Task::ready(Ok(None));
|
||||
};
|
||||
if self.is_local() {
|
||||
Some(worktree.update(cx, |worktree, cx| {
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
worktree
|
||||
.as_local_mut()
|
||||
.unwrap()
|
||||
.create_entry(project_path.path, is_directory, cx)
|
||||
}))
|
||||
})
|
||||
} else {
|
||||
let client = self.client.clone();
|
||||
let project_id = self.remote_id().unwrap();
|
||||
Some(cx.spawn(move |_, mut cx| async move {
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let response = client
|
||||
.request(proto::CreateProjectEntry {
|
||||
worktree_id: project_path.worktree_id.to_proto(),
|
||||
|
@ -1173,19 +1175,20 @@ impl Project {
|
|||
is_directory,
|
||||
})
|
||||
.await?;
|
||||
let entry = response
|
||||
.entry
|
||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
||||
worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree.as_remote_mut().unwrap().insert_entry(
|
||||
entry,
|
||||
response.worktree_scan_id as usize,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
}))
|
||||
match response.entry {
|
||||
Some(entry) => worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree.as_remote_mut().unwrap().insert_entry(
|
||||
entry,
|
||||
response.worktree_scan_id as usize,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1194,8 +1197,10 @@ impl Project {
|
|||
entry_id: ProjectEntryId,
|
||||
new_path: impl Into<Arc<Path>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<Task<Result<Entry>>> {
|
||||
let worktree = self.worktree_for_entry(entry_id, cx)?;
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
|
||||
return Task::ready(Ok(None));
|
||||
};
|
||||
let new_path = new_path.into();
|
||||
if self.is_local() {
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
|
@ -1208,7 +1213,7 @@ impl Project {
|
|||
let client = self.client.clone();
|
||||
let project_id = self.remote_id().unwrap();
|
||||
|
||||
Some(cx.spawn(move |_, mut cx| async move {
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let response = client
|
||||
.request(proto::CopyProjectEntry {
|
||||
project_id,
|
||||
|
@ -1216,19 +1221,20 @@ impl Project {
|
|||
new_path: new_path.to_string_lossy().into(),
|
||||
})
|
||||
.await?;
|
||||
let entry = response
|
||||
.entry
|
||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
||||
worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree.as_remote_mut().unwrap().insert_entry(
|
||||
entry,
|
||||
response.worktree_scan_id as usize,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
}))
|
||||
match response.entry {
|
||||
Some(entry) => worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree.as_remote_mut().unwrap().insert_entry(
|
||||
entry,
|
||||
response.worktree_scan_id as usize,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1237,8 +1243,10 @@ impl Project {
|
|||
entry_id: ProjectEntryId,
|
||||
new_path: impl Into<Arc<Path>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<Task<Result<Entry>>> {
|
||||
let worktree = self.worktree_for_entry(entry_id, cx)?;
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
|
||||
return Task::ready(Ok(None));
|
||||
};
|
||||
let new_path = new_path.into();
|
||||
if self.is_local() {
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
|
@ -1251,7 +1259,7 @@ impl Project {
|
|||
let client = self.client.clone();
|
||||
let project_id = self.remote_id().unwrap();
|
||||
|
||||
Some(cx.spawn(move |_, mut cx| async move {
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let response = client
|
||||
.request(proto::RenameProjectEntry {
|
||||
project_id,
|
||||
|
@ -1259,19 +1267,20 @@ impl Project {
|
|||
new_path: new_path.to_string_lossy().into(),
|
||||
})
|
||||
.await?;
|
||||
let entry = response
|
||||
.entry
|
||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
||||
worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree.as_remote_mut().unwrap().insert_entry(
|
||||
entry,
|
||||
response.worktree_scan_id as usize,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
}))
|
||||
match response.entry {
|
||||
Some(entry) => worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree.as_remote_mut().unwrap().insert_entry(
|
||||
entry,
|
||||
response.worktree_scan_id as usize,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1688,18 +1697,15 @@ impl Project {
|
|||
|
||||
pub fn open_path(
|
||||
&mut self,
|
||||
path: impl Into<ProjectPath>,
|
||||
path: ProjectPath,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<(ProjectEntryId, AnyModel)>> {
|
||||
let project_path = path.into();
|
||||
let task = self.open_buffer(project_path.clone(), cx);
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
) -> Task<Result<(Option<ProjectEntryId>, AnyModel)>> {
|
||||
let task = self.open_buffer(path.clone(), cx);
|
||||
cx.spawn(move |_, cx| async move {
|
||||
let buffer = task.await?;
|
||||
let project_entry_id = buffer
|
||||
.update(&mut cx, |buffer, cx| {
|
||||
File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
|
||||
})?
|
||||
.with_context(|| format!("no project entry for {project_path:?}"))?;
|
||||
let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
|
||||
File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
|
||||
})?;
|
||||
|
||||
let buffer: &AnyModel = &buffer;
|
||||
Ok((project_entry_id, buffer.clone()))
|
||||
|
@ -2018,8 +2024,10 @@ impl Project {
|
|||
remote_id,
|
||||
);
|
||||
|
||||
self.local_buffer_ids_by_entry_id
|
||||
.insert(file.entry_id, remote_id);
|
||||
if let Some(entry_id) = file.entry_id {
|
||||
self.local_buffer_ids_by_entry_id
|
||||
.insert(entry_id, remote_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2474,24 +2482,25 @@ impl Project {
|
|||
return None;
|
||||
};
|
||||
|
||||
match self.local_buffer_ids_by_entry_id.get(&file.entry_id) {
|
||||
Some(_) => {
|
||||
return None;
|
||||
let remote_id = buffer.read(cx).remote_id();
|
||||
if let Some(entry_id) = file.entry_id {
|
||||
match self.local_buffer_ids_by_entry_id.get(&entry_id) {
|
||||
Some(_) => {
|
||||
return None;
|
||||
}
|
||||
None => {
|
||||
self.local_buffer_ids_by_entry_id
|
||||
.insert(entry_id, remote_id);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let remote_id = buffer.read(cx).remote_id();
|
||||
self.local_buffer_ids_by_entry_id
|
||||
.insert(file.entry_id, remote_id);
|
||||
|
||||
self.local_buffer_ids_by_path.insert(
|
||||
ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path.clone(),
|
||||
},
|
||||
remote_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
self.local_buffer_ids_by_path.insert(
|
||||
ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path.clone(),
|
||||
},
|
||||
remote_id,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -5845,11 +5854,6 @@ impl Project {
|
|||
while let Some(ignored_abs_path) =
|
||||
ignored_paths_to_process.pop_front()
|
||||
{
|
||||
if !query.file_matches(Some(&ignored_abs_path))
|
||||
|| snapshot.is_path_excluded(&ignored_abs_path)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(fs_metadata) = fs
|
||||
.metadata(&ignored_abs_path)
|
||||
.await
|
||||
|
@ -5877,6 +5881,13 @@ impl Project {
|
|||
}
|
||||
}
|
||||
} else if !fs_metadata.is_symlink {
|
||||
if !query.file_matches(Some(&ignored_abs_path))
|
||||
|| snapshot.is_path_excluded(
|
||||
ignored_entry.path.to_path_buf(),
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let matches = if let Some(file) = fs
|
||||
.open_sync(&ignored_abs_path)
|
||||
.await
|
||||
|
@ -6278,10 +6289,13 @@ impl Project {
|
|||
return;
|
||||
}
|
||||
|
||||
let new_file = if let Some(entry) = snapshot.entry_for_id(old_file.entry_id) {
|
||||
let new_file = if let Some(entry) = old_file
|
||||
.entry_id
|
||||
.and_then(|entry_id| snapshot.entry_for_id(entry_id))
|
||||
{
|
||||
File {
|
||||
is_local: true,
|
||||
entry_id: entry.id,
|
||||
entry_id: Some(entry.id),
|
||||
mtime: entry.mtime,
|
||||
path: entry.path.clone(),
|
||||
worktree: worktree_handle.clone(),
|
||||
|
@ -6290,7 +6304,7 @@ impl Project {
|
|||
} else if let Some(entry) = snapshot.entry_for_path(old_file.path().as_ref()) {
|
||||
File {
|
||||
is_local: true,
|
||||
entry_id: entry.id,
|
||||
entry_id: Some(entry.id),
|
||||
mtime: entry.mtime,
|
||||
path: entry.path.clone(),
|
||||
worktree: worktree_handle.clone(),
|
||||
|
@ -6320,10 +6334,12 @@ impl Project {
|
|||
);
|
||||
}
|
||||
|
||||
if new_file.entry_id != *entry_id {
|
||||
if new_file.entry_id != Some(*entry_id) {
|
||||
self.local_buffer_ids_by_entry_id.remove(entry_id);
|
||||
self.local_buffer_ids_by_entry_id
|
||||
.insert(new_file.entry_id, buffer_id);
|
||||
if let Some(entry_id) = new_file.entry_id {
|
||||
self.local_buffer_ids_by_entry_id
|
||||
.insert(entry_id, buffer_id);
|
||||
}
|
||||
}
|
||||
|
||||
if new_file != *old_file {
|
||||
|
@ -6890,7 +6906,7 @@ impl Project {
|
|||
})?
|
||||
.await?;
|
||||
Ok(proto::ProjectEntryResponse {
|
||||
entry: Some((&entry).into()),
|
||||
entry: entry.as_ref().map(|e| e.into()),
|
||||
worktree_scan_id: worktree_scan_id as u64,
|
||||
})
|
||||
}
|
||||
|
@ -6914,11 +6930,10 @@ impl Project {
|
|||
.as_local_mut()
|
||||
.unwrap()
|
||||
.rename_entry(entry_id, new_path, cx)
|
||||
.ok_or_else(|| anyhow!("invalid entry"))
|
||||
})??
|
||||
})?
|
||||
.await?;
|
||||
Ok(proto::ProjectEntryResponse {
|
||||
entry: Some((&entry).into()),
|
||||
entry: entry.as_ref().map(|e| e.into()),
|
||||
worktree_scan_id: worktree_scan_id as u64,
|
||||
})
|
||||
}
|
||||
|
@ -6942,11 +6957,10 @@ impl Project {
|
|||
.as_local_mut()
|
||||
.unwrap()
|
||||
.copy_entry(entry_id, new_path, cx)
|
||||
.ok_or_else(|| anyhow!("invalid entry"))
|
||||
})??
|
||||
})?
|
||||
.await?;
|
||||
Ok(proto::ProjectEntryResponse {
|
||||
entry: Some((&entry).into()),
|
||||
entry: entry.as_ref().map(|e| e.into()),
|
||||
worktree_scan_id: worktree_scan_id as u64,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4182,6 +4182,94 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_search_in_gitignored_dirs(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
".git": {},
|
||||
".gitignore": "**/target\n/node_modules\n",
|
||||
"target": {
|
||||
"index.txt": "index_key:index_value"
|
||||
},
|
||||
"node_modules": {
|
||||
"eslint": {
|
||||
"index.ts": "const eslint_key = 'eslint value'",
|
||||
"package.json": r#"{ "some_key": "some value" }"#,
|
||||
},
|
||||
"prettier": {
|
||||
"index.ts": "const prettier_key = 'prettier value'",
|
||||
"package.json": r#"{ "other_key": "other value" }"#,
|
||||
},
|
||||
},
|
||||
"package.json": r#"{ "main_key": "main value" }"#,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
|
||||
let query = "key";
|
||||
assert_eq!(
|
||||
search(
|
||||
&project,
|
||||
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new()).unwrap(),
|
||||
cx
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
HashMap::from_iter([("package.json".to_string(), vec![8..11])]),
|
||||
"Only one non-ignored file should have the query"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
search(
|
||||
&project,
|
||||
SearchQuery::text(query, false, false, true, Vec::new(), Vec::new()).unwrap(),
|
||||
cx
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
HashMap::from_iter([
|
||||
("package.json".to_string(), vec![8..11]),
|
||||
("target/index.txt".to_string(), vec![6..9]),
|
||||
(
|
||||
"node_modules/prettier/package.json".to_string(),
|
||||
vec![9..12]
|
||||
),
|
||||
("node_modules/prettier/index.ts".to_string(), vec![15..18]),
|
||||
("node_modules/eslint/index.ts".to_string(), vec![13..16]),
|
||||
("node_modules/eslint/package.json".to_string(), vec![8..11]),
|
||||
]),
|
||||
"Unrestricted search with ignored directories should find every file with the query"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
search(
|
||||
&project,
|
||||
SearchQuery::text(
|
||||
query,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
vec![PathMatcher::new("node_modules/prettier/**").unwrap()],
|
||||
vec![PathMatcher::new("*.ts").unwrap()],
|
||||
)
|
||||
.unwrap(),
|
||||
cx
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
HashMap::from_iter([(
|
||||
"node_modules/prettier/package.json".to_string(),
|
||||
vec![9..12]
|
||||
)]),
|
||||
"With search including ignored prettier directory and excluding TS files, only one file should be found"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_literal_prefix() {
|
||||
assert_eq!(glob_literal_prefix("**/*.js"), "");
|
||||
|
|
|
@ -371,15 +371,25 @@ impl SearchQuery {
|
|||
pub fn file_matches(&self, file_path: Option<&Path>) -> bool {
|
||||
match file_path {
|
||||
Some(file_path) => {
|
||||
!self
|
||||
.files_to_exclude()
|
||||
.iter()
|
||||
.any(|exclude_glob| exclude_glob.is_match(file_path))
|
||||
&& (self.files_to_include().is_empty()
|
||||
let mut path = file_path.to_path_buf();
|
||||
loop {
|
||||
if self
|
||||
.files_to_exclude()
|
||||
.iter()
|
||||
.any(|exclude_glob| exclude_glob.is_match(&path))
|
||||
{
|
||||
return false;
|
||||
} else if self.files_to_include().is_empty()
|
||||
|| self
|
||||
.files_to_include()
|
||||
.iter()
|
||||
.any(|include_glob| include_glob.is_match(file_path)))
|
||||
.any(|include_glob| include_glob.is_match(&path))
|
||||
{
|
||||
return true;
|
||||
} else if !path.pop() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => self.files_to_include().is_empty(),
|
||||
}
|
||||
|
|
|
@ -958,8 +958,6 @@ impl LocalWorktree {
|
|||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let text = fs.load(&abs_path).await?;
|
||||
let entry = entry.await?;
|
||||
|
||||
let mut index_task = None;
|
||||
let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?;
|
||||
if let Some(repo) = snapshot.repository_for_path(&path) {
|
||||
|
@ -982,18 +980,43 @@ impl LocalWorktree {
|
|||
let worktree = this
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("worktree was dropped"))?;
|
||||
Ok((
|
||||
File {
|
||||
entry_id: entry.id,
|
||||
worktree,
|
||||
path: entry.path,
|
||||
mtime: entry.mtime,
|
||||
is_local: true,
|
||||
is_deleted: false,
|
||||
},
|
||||
text,
|
||||
diff_base,
|
||||
))
|
||||
match entry.await? {
|
||||
Some(entry) => Ok((
|
||||
File {
|
||||
entry_id: Some(entry.id),
|
||||
worktree,
|
||||
path: entry.path,
|
||||
mtime: entry.mtime,
|
||||
is_local: true,
|
||||
is_deleted: false,
|
||||
},
|
||||
text,
|
||||
diff_base,
|
||||
)),
|
||||
None => {
|
||||
let metadata = fs
|
||||
.metadata(&abs_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Loading metadata for excluded file {abs_path:?}")
|
||||
})?
|
||||
.with_context(|| {
|
||||
format!("Excluded file {abs_path:?} got removed during loading")
|
||||
})?;
|
||||
Ok((
|
||||
File {
|
||||
entry_id: None,
|
||||
worktree,
|
||||
path,
|
||||
mtime: metadata.mtime,
|
||||
is_local: true,
|
||||
is_deleted: false,
|
||||
},
|
||||
text,
|
||||
diff_base,
|
||||
))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1013,18 +1036,38 @@ impl LocalWorktree {
|
|||
let text = buffer.as_rope().clone();
|
||||
let fingerprint = text.fingerprint();
|
||||
let version = buffer.version();
|
||||
let save = self.write_file(path, text, buffer.line_ending(), cx);
|
||||
let save = self.write_file(path.as_ref(), text, buffer.line_ending(), cx);
|
||||
let fs = Arc::clone(&self.fs);
|
||||
let abs_path = self.absolutize(&path);
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let entry = save.await?;
|
||||
let this = this.upgrade().context("worktree dropped")?;
|
||||
|
||||
let (entry_id, mtime, path) = match entry {
|
||||
Some(entry) => (Some(entry.id), entry.mtime, entry.path),
|
||||
None => {
|
||||
let metadata = fs
|
||||
.metadata(&abs_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Fetching metadata after saving the excluded buffer {abs_path:?}"
|
||||
)
|
||||
})?
|
||||
.with_context(|| {
|
||||
format!("Excluded buffer {path:?} got removed during saving")
|
||||
})?;
|
||||
(None, metadata.mtime, path)
|
||||
}
|
||||
};
|
||||
|
||||
if has_changed_file {
|
||||
let new_file = Arc::new(File {
|
||||
entry_id: entry.id,
|
||||
entry_id,
|
||||
worktree: this,
|
||||
path: entry.path,
|
||||
mtime: entry.mtime,
|
||||
path,
|
||||
mtime,
|
||||
is_local: true,
|
||||
is_deleted: false,
|
||||
});
|
||||
|
@ -1050,13 +1093,13 @@ impl LocalWorktree {
|
|||
project_id,
|
||||
buffer_id,
|
||||
version: serialize_version(&version),
|
||||
mtime: Some(entry.mtime.into()),
|
||||
mtime: Some(mtime.into()),
|
||||
fingerprint: serialize_fingerprint(fingerprint),
|
||||
})?;
|
||||
}
|
||||
|
||||
buffer_handle.update(&mut cx, |buffer, cx| {
|
||||
buffer.did_save(version.clone(), fingerprint, entry.mtime, cx);
|
||||
buffer.did_save(version.clone(), fingerprint, mtime, cx);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
@ -1081,7 +1124,7 @@ impl LocalWorktree {
|
|||
path: impl Into<Arc<Path>>,
|
||||
is_dir: bool,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Entry>> {
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
let path = path.into();
|
||||
let lowest_ancestor = self.lowest_ancestor(&path);
|
||||
let abs_path = self.absolutize(&path);
|
||||
|
@ -1098,7 +1141,7 @@ impl LocalWorktree {
|
|||
cx.spawn(|this, mut cx| async move {
|
||||
write.await?;
|
||||
let (result, refreshes) = this.update(&mut cx, |this, cx| {
|
||||
let mut refreshes = Vec::<Task<anyhow::Result<Entry>>>::new();
|
||||
let mut refreshes = Vec::new();
|
||||
let refresh_paths = path.strip_prefix(&lowest_ancestor).unwrap();
|
||||
for refresh_path in refresh_paths.ancestors() {
|
||||
if refresh_path == Path::new("") {
|
||||
|
@ -1125,14 +1168,14 @@ impl LocalWorktree {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn write_file(
|
||||
pub(crate) fn write_file(
|
||||
&self,
|
||||
path: impl Into<Arc<Path>>,
|
||||
text: Rope,
|
||||
line_ending: LineEnding,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Entry>> {
|
||||
let path = path.into();
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
let path: Arc<Path> = path.into();
|
||||
let abs_path = self.absolutize(&path);
|
||||
let fs = self.fs.clone();
|
||||
let write = cx
|
||||
|
@ -1191,8 +1234,11 @@ impl LocalWorktree {
|
|||
entry_id: ProjectEntryId,
|
||||
new_path: impl Into<Arc<Path>>,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Option<Task<Result<Entry>>> {
|
||||
let old_path = self.entry_for_id(entry_id)?.path.clone();
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
let old_path = match self.entry_for_id(entry_id) {
|
||||
Some(entry) => entry.path.clone(),
|
||||
None => return Task::ready(Ok(None)),
|
||||
};
|
||||
let new_path = new_path.into();
|
||||
let abs_old_path = self.absolutize(&old_path);
|
||||
let abs_new_path = self.absolutize(&new_path);
|
||||
|
@ -1202,7 +1248,7 @@ impl LocalWorktree {
|
|||
.await
|
||||
});
|
||||
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
rename.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.as_local_mut()
|
||||
|
@ -1210,7 +1256,7 @@ impl LocalWorktree {
|
|||
.refresh_entry(new_path.clone(), Some(old_path), cx)
|
||||
})?
|
||||
.await
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn copy_entry(
|
||||
|
@ -1218,8 +1264,11 @@ impl LocalWorktree {
|
|||
entry_id: ProjectEntryId,
|
||||
new_path: impl Into<Arc<Path>>,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Option<Task<Result<Entry>>> {
|
||||
let old_path = self.entry_for_id(entry_id)?.path.clone();
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
let old_path = match self.entry_for_id(entry_id) {
|
||||
Some(entry) => entry.path.clone(),
|
||||
None => return Task::ready(Ok(None)),
|
||||
};
|
||||
let new_path = new_path.into();
|
||||
let abs_old_path = self.absolutize(&old_path);
|
||||
let abs_new_path = self.absolutize(&new_path);
|
||||
|
@ -1234,7 +1283,7 @@ impl LocalWorktree {
|
|||
.await
|
||||
});
|
||||
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
copy.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.as_local_mut()
|
||||
|
@ -1242,7 +1291,7 @@ impl LocalWorktree {
|
|||
.refresh_entry(new_path.clone(), None, cx)
|
||||
})?
|
||||
.await
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn expand_entry(
|
||||
|
@ -1278,7 +1327,10 @@ impl LocalWorktree {
|
|||
path: Arc<Path>,
|
||||
old_path: Option<Arc<Path>>,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Entry>> {
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
if self.is_path_excluded(path.to_path_buf()) {
|
||||
return Task::ready(Ok(None));
|
||||
}
|
||||
let paths = if let Some(old_path) = old_path.as_ref() {
|
||||
vec![old_path.clone(), path.clone()]
|
||||
} else {
|
||||
|
@ -1287,11 +1339,12 @@ impl LocalWorktree {
|
|||
let mut refresh = self.refresh_entries_for_paths(paths);
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
refresh.recv().await;
|
||||
this.update(&mut cx, |this, _| {
|
||||
let new_entry = this.update(&mut cx, |this, _| {
|
||||
this.entry_for_path(path)
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow!("failed to read path after update"))
|
||||
})?
|
||||
})??;
|
||||
Ok(Some(new_entry))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2222,10 +2275,19 @@ impl LocalSnapshot {
|
|||
paths
|
||||
}
|
||||
|
||||
pub fn is_path_excluded(&self, abs_path: &Path) -> bool {
|
||||
self.file_scan_exclusions
|
||||
.iter()
|
||||
.any(|exclude_matcher| exclude_matcher.is_match(abs_path))
|
||||
pub fn is_path_excluded(&self, mut path: PathBuf) -> bool {
|
||||
loop {
|
||||
if self
|
||||
.file_scan_exclusions
|
||||
.iter()
|
||||
.any(|exclude_matcher| exclude_matcher.is_match(&path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if !path.pop() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2455,8 +2517,7 @@ impl BackgroundScannerState {
|
|||
ids_to_preserve.insert(work_directory_id);
|
||||
} else {
|
||||
let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);
|
||||
let git_dir_excluded = snapshot.is_path_excluded(&entry.git_dir_path)
|
||||
|| snapshot.is_path_excluded(&git_dir_abs_path);
|
||||
let git_dir_excluded = snapshot.is_path_excluded(entry.git_dir_path.to_path_buf());
|
||||
if git_dir_excluded
|
||||
&& !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
|
||||
{
|
||||
|
@ -2663,7 +2724,7 @@ pub struct File {
|
|||
pub worktree: Model<Worktree>,
|
||||
pub path: Arc<Path>,
|
||||
pub mtime: SystemTime,
|
||||
pub(crate) entry_id: ProjectEntryId,
|
||||
pub(crate) entry_id: Option<ProjectEntryId>,
|
||||
pub(crate) is_local: bool,
|
||||
pub(crate) is_deleted: bool,
|
||||
}
|
||||
|
@ -2732,7 +2793,7 @@ impl language::File for File {
|
|||
fn to_proto(&self) -> rpc::proto::File {
|
||||
rpc::proto::File {
|
||||
worktree_id: self.worktree.entity_id().as_u64(),
|
||||
entry_id: self.entry_id.to_proto(),
|
||||
entry_id: self.entry_id.map(|id| id.to_proto()),
|
||||
path: self.path.to_string_lossy().into(),
|
||||
mtime: Some(self.mtime.into()),
|
||||
is_deleted: self.is_deleted,
|
||||
|
@ -2790,7 +2851,7 @@ impl File {
|
|||
worktree,
|
||||
path: entry.path.clone(),
|
||||
mtime: entry.mtime,
|
||||
entry_id: entry.id,
|
||||
entry_id: Some(entry.id),
|
||||
is_local: true,
|
||||
is_deleted: false,
|
||||
})
|
||||
|
@ -2815,7 +2876,7 @@ impl File {
|
|||
worktree,
|
||||
path: Path::new(&proto.path).into(),
|
||||
mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
|
||||
entry_id: ProjectEntryId::from_proto(proto.entry_id),
|
||||
entry_id: proto.entry_id.map(ProjectEntryId::from_proto),
|
||||
is_local: false,
|
||||
is_deleted: proto.is_deleted,
|
||||
})
|
||||
|
@ -2833,7 +2894,7 @@ impl File {
|
|||
if self.is_deleted {
|
||||
None
|
||||
} else {
|
||||
Some(self.entry_id)
|
||||
self.entry_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3329,16 +3390,7 @@ impl BackgroundScanner {
|
|||
return false;
|
||||
}
|
||||
|
||||
// FS events may come for files which parent directory is excluded, need to check ignore those.
|
||||
let mut path_to_test = abs_path.clone();
|
||||
let mut excluded_file_event = snapshot.is_path_excluded(abs_path)
|
||||
|| snapshot.is_path_excluded(&relative_path);
|
||||
while !excluded_file_event && path_to_test.pop() {
|
||||
if snapshot.is_path_excluded(&path_to_test) {
|
||||
excluded_file_event = true;
|
||||
}
|
||||
}
|
||||
if excluded_file_event {
|
||||
if snapshot.is_path_excluded(relative_path.to_path_buf()) {
|
||||
if !is_git_related {
|
||||
log::debug!("ignoring FS event for excluded path {relative_path:?}");
|
||||
}
|
||||
|
@ -3522,7 +3574,7 @@ impl BackgroundScanner {
|
|||
let state = self.state.lock();
|
||||
let snapshot = &state.snapshot;
|
||||
root_abs_path = snapshot.abs_path().clone();
|
||||
if snapshot.is_path_excluded(&job.abs_path) {
|
||||
if snapshot.is_path_excluded(job.path.to_path_buf()) {
|
||||
log::error!("skipping excluded directory {:?}", job.path);
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -3593,9 +3645,9 @@ impl BackgroundScanner {
|
|||
}
|
||||
|
||||
{
|
||||
let relative_path = job.path.join(child_name);
|
||||
let mut state = self.state.lock();
|
||||
if state.snapshot.is_path_excluded(&child_abs_path) {
|
||||
let relative_path = job.path.join(child_name);
|
||||
if state.snapshot.is_path_excluded(relative_path.clone()) {
|
||||
log::debug!("skipping excluded child entry {relative_path:?}");
|
||||
state.remove_path(&relative_path);
|
||||
continue;
|
||||
|
|
|
@ -1055,11 +1055,12 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||
&[
|
||||
".git/HEAD",
|
||||
".git/foo",
|
||||
"node_modules",
|
||||
"node_modules/.DS_Store",
|
||||
"node_modules/prettier",
|
||||
"node_modules/prettier/package.json",
|
||||
],
|
||||
&["target", "node_modules"],
|
||||
&["target"],
|
||||
&[
|
||||
".DS_Store",
|
||||
"src/.DS_Store",
|
||||
|
@ -1109,6 +1110,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||
".git/HEAD",
|
||||
".git/foo",
|
||||
".git/new_file",
|
||||
"node_modules",
|
||||
"node_modules/.DS_Store",
|
||||
"node_modules/prettier",
|
||||
"node_modules/prettier/package.json",
|
||||
|
@ -1117,7 +1119,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||
"build_output/new_file",
|
||||
"test_output/new_file",
|
||||
],
|
||||
&["target", "node_modules", "test_output"],
|
||||
&["target", "test_output"],
|
||||
&[
|
||||
".DS_Store",
|
||||
"src/.DS_Store",
|
||||
|
@ -1177,6 +1179,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|||
.create_entry("a/e".as_ref(), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(entry.is_dir());
|
||||
|
||||
|
@ -1226,6 +1229,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
|
@ -1261,6 +1265,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
|
@ -1279,6 +1284,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
.create_entry("a/b/c/e.txt".as_ref(), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
|
@ -1295,6 +1301,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
.create_entry("d/e/f/g.txt".as_ref(), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
|
@ -1620,14 +1627,14 @@ fn randomly_mutate_worktree(
|
|||
entry.id.0,
|
||||
new_path
|
||||
);
|
||||
let task = worktree.rename_entry(entry.id, new_path, cx).unwrap();
|
||||
let task = worktree.rename_entry(entry.id, new_path, cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
task.await?;
|
||||
task.await?.unwrap();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let task = if entry.is_dir() {
|
||||
if entry.is_dir() {
|
||||
let child_path = entry.path.join(random_filename(rng));
|
||||
let is_dir = rng.gen_bool(0.3);
|
||||
log::info!(
|
||||
|
@ -1635,15 +1642,20 @@ fn randomly_mutate_worktree(
|
|||
if is_dir { "dir" } else { "file" },
|
||||
child_path,
|
||||
);
|
||||
worktree.create_entry(child_path, is_dir, cx)
|
||||
let task = worktree.create_entry(child_path, is_dir, cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
|
||||
worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx)
|
||||
};
|
||||
cx.background_executor().spawn(async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
})
|
||||
let task =
|
||||
worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue