Merge pull request #1050 from zed-industries/ignored-files
Show ignored entries in project panel
This commit is contained in:
commit
5c4bd9393f
5 changed files with 178 additions and 30 deletions
|
@ -1744,8 +1744,13 @@ mod tests {
|
|||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
".gitignore": "ignored-dir",
|
||||
"a.txt": "a-contents",
|
||||
"b.txt": "b-contents",
|
||||
"ignored-dir": {
|
||||
"c.txt": "",
|
||||
"d.txt": "",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
@ -1775,7 +1780,6 @@ mod tests {
|
|||
// Join that project as client B
|
||||
let client_b_peer_id = client_b.peer_id;
|
||||
let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
|
||||
|
||||
let replica_id_b = project_b.read_with(cx_b, |project, _| {
|
||||
assert_eq!(
|
||||
project
|
||||
|
@ -1788,16 +1792,27 @@ mod tests {
|
|||
);
|
||||
project.replica_id()
|
||||
});
|
||||
project_a
|
||||
.condition(&cx_a, |tree, _| {
|
||||
tree.collaborators()
|
||||
.get(&client_b_peer_id)
|
||||
.map_or(false, |collaborator| {
|
||||
collaborator.replica_id == replica_id_b
|
||||
&& collaborator.user.github_login == "user_b"
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
deterministic.run_until_parked();
|
||||
project_a.read_with(cx_a, |project, _| {
|
||||
let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
|
||||
assert_eq!(client_b_collaborator.replica_id, replica_id_b);
|
||||
assert_eq!(client_b_collaborator.user.github_login, "user_b");
|
||||
});
|
||||
project_b.read_with(cx_b, |project, cx| {
|
||||
let worktree = project.worktrees(cx).next().unwrap().read(cx);
|
||||
assert_eq!(
|
||||
worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
|
||||
[
|
||||
Path::new(".gitignore"),
|
||||
Path::new("a.txt"),
|
||||
Path::new("b.txt"),
|
||||
Path::new("ignored-dir"),
|
||||
Path::new("ignored-dir/c.txt"),
|
||||
Path::new("ignored-dir/d.txt"),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// Open the same file as client B and client A.
|
||||
let buffer_b = project_b
|
||||
|
|
|
@ -40,6 +40,7 @@ use std::{
|
|||
ffi::{OsStr, OsString},
|
||||
fmt,
|
||||
future::Future,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
os::unix::prelude::{OsStrExt, OsStringExt},
|
||||
path::{Path, PathBuf},
|
||||
|
@ -827,8 +828,8 @@ impl LocalWorktree {
|
|||
next_entry_id = snapshot.next_entry_id.clone();
|
||||
}
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
let entry = Entry::new(
|
||||
path,
|
||||
let mut entry = Entry::new(
|
||||
path.clone(),
|
||||
&fs.metadata(&abs_path)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("could not read saved file metadata"))?,
|
||||
|
@ -842,6 +843,9 @@ impl LocalWorktree {
|
|||
let (entry, snapshot, snapshots_tx) = this.read_with(&cx, |this, _| {
|
||||
let this = this.as_local().unwrap();
|
||||
let mut snapshot = this.background_snapshot.lock();
|
||||
entry.is_ignored = snapshot
|
||||
.ignore_stack_for_path(&path, entry.is_dir())
|
||||
.is_path_ignored(&path, entry.is_dir());
|
||||
if let Some(old_path) = old_path {
|
||||
snapshot.remove_path(&old_path);
|
||||
}
|
||||
|
@ -951,9 +955,43 @@ impl LocalWorktree {
|
|||
})?;
|
||||
}
|
||||
|
||||
// Stream ignored entries in chunks.
|
||||
{
|
||||
let mut ignored_entries = prev_snapshot
|
||||
.entries_by_path
|
||||
.iter()
|
||||
.filter(|e| e.is_ignored);
|
||||
let mut ignored_entries_to_send = Vec::new();
|
||||
loop {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
const CHUNK_SIZE: usize = 2;
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
const CHUNK_SIZE: usize = 256;
|
||||
|
||||
let entry = ignored_entries.next();
|
||||
if ignored_entries_to_send.len() >= CHUNK_SIZE || entry.is_none() {
|
||||
rpc.request(proto::UpdateWorktree {
|
||||
project_id,
|
||||
worktree_id,
|
||||
root_name: prev_snapshot.root_name().to_string(),
|
||||
updated_entries: mem::take(&mut ignored_entries_to_send),
|
||||
removed_entries: Default::default(),
|
||||
scan_id: prev_snapshot.scan_id as u64,
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(entry) = entry {
|
||||
ignored_entries_to_send.push(entry.into());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Ok(snapshot) = snapshots_to_send_rx.recv().await {
|
||||
let message =
|
||||
snapshot.build_update(&prev_snapshot, project_id, worktree_id, false);
|
||||
snapshot.build_update(&prev_snapshot, project_id, worktree_id, true);
|
||||
rpc.request(message).await?;
|
||||
prev_snapshot = snapshot;
|
||||
}
|
||||
|
@ -1905,6 +1943,7 @@ impl sum_tree::Summary for EntrySummary {
|
|||
|
||||
fn add_summary(&mut self, rhs: &Self, _: &()) {
|
||||
self.max_path = rhs.max_path.clone();
|
||||
self.count += rhs.count;
|
||||
self.visible_count += rhs.visible_count;
|
||||
self.file_count += rhs.file_count;
|
||||
self.visible_file_count += rhs.visible_file_count;
|
||||
|
@ -2675,6 +2714,7 @@ mod tests {
|
|||
use anyhow::Result;
|
||||
use client::test::FakeHttpClient;
|
||||
use fs::RealFs;
|
||||
use gpui::TestAppContext;
|
||||
use rand::prelude::*;
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
|
@ -2685,7 +2725,7 @@ mod tests {
|
|||
use util::test::temp_tree;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_traversal(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_traversal(cx: &mut TestAppContext) {
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
|
@ -2727,11 +2767,23 @@ mod tests {
|
|||
Path::new("a/c"),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.map(|entry| entry.path.as_ref())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new(""),
|
||||
Path::new(".gitignore"),
|
||||
Path::new("a"),
|
||||
Path::new("a/b"),
|
||||
Path::new("a/c"),
|
||||
]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_rescan_with_gitignore(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
".git": {},
|
||||
".gitignore": "ignored-dir\n",
|
||||
|
@ -2781,6 +2833,59 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_write_file(cx: &mut TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
".git": {},
|
||||
".gitignore": "ignored-dir\n",
|
||||
"tracked-dir": {},
|
||||
"ignored-dir": {}
|
||||
}));
|
||||
|
||||
let http_client = FakeHttpClient::with_404_response();
|
||||
let client = Client::new(http_client.clone());
|
||||
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
dir.path(),
|
||||
true,
|
||||
Arc::new(RealFs),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
tree.flush_fs_events(&cx).await;
|
||||
|
||||
tree.update(cx, |tree, cx| {
|
||||
tree.as_local().unwrap().write_file(
|
||||
Path::new("tracked-dir/file.txt"),
|
||||
"hello".into(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
tree.update(cx, |tree, cx| {
|
||||
tree.as_local().unwrap().write_file(
|
||||
Path::new("ignored-dir/file.txt"),
|
||||
"world".into(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap();
|
||||
let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap();
|
||||
assert_eq!(tracked.is_ignored, false);
|
||||
assert_eq!(ignored.is_ignored, true);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random(mut rng: StdRng) {
|
||||
let operations = env::var("OPERATIONS")
|
||||
|
@ -3072,12 +3177,18 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
let dfs_paths = self
|
||||
let dfs_paths_via_iter = self
|
||||
.entries_by_path
|
||||
.cursor::<()>()
|
||||
.map(|e| e.path.as_ref())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(bfs_paths, dfs_paths);
|
||||
assert_eq!(bfs_paths, dfs_paths_via_iter);
|
||||
|
||||
let dfs_paths_via_traversal = self
|
||||
.entries(true)
|
||||
.map(|e| e.path.as_ref())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(dfs_paths_via_traversal, dfs_paths_via_iter);
|
||||
|
||||
for (ignore_parent_path, _) in &self.ignores {
|
||||
assert!(self.entry_for_path(ignore_parent_path).is_some());
|
||||
|
|
|
@ -59,6 +59,7 @@ struct EntryDetails {
|
|||
filename: String,
|
||||
depth: usize,
|
||||
kind: EntryKind,
|
||||
is_ignored: bool,
|
||||
is_expanded: bool,
|
||||
is_selected: bool,
|
||||
is_editing: bool,
|
||||
|
@ -613,7 +614,7 @@ impl ProjectPanel {
|
|||
}
|
||||
|
||||
let mut visible_worktree_entries = Vec::new();
|
||||
let mut entry_iter = snapshot.entries(false);
|
||||
let mut entry_iter = snapshot.entries(true);
|
||||
while let Some(entry) = entry_iter.entry() {
|
||||
visible_worktree_entries.push(entry.clone());
|
||||
if Some(entry.id) == new_entry_parent_id {
|
||||
|
@ -739,6 +740,7 @@ impl ProjectPanel {
|
|||
.to_string(),
|
||||
depth: entry.path.components().count(),
|
||||
kind: entry.kind,
|
||||
is_ignored: entry.is_ignored,
|
||||
is_expanded: expanded_entry_ids.binary_search(&entry.id).is_ok(),
|
||||
is_selected: self.selection.map_or(false, |e| {
|
||||
e.worktree_id == snapshot.id() && e.entry_id == entry.id
|
||||
|
@ -784,7 +786,11 @@ impl ProjectPanel {
|
|||
let show_editor = details.is_editing && !details.is_processing;
|
||||
MouseEventHandler::new::<Self, _, _>(entry_id.to_usize(), cx, |state, _| {
|
||||
let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width;
|
||||
let style = theme.entry.style_for(state, details.is_selected);
|
||||
let mut style = theme.entry.style_for(state, details.is_selected).clone();
|
||||
if details.is_ignored {
|
||||
style.text.color.fade_out(theme.ignored_entry_fade);
|
||||
style.icon_color.fade_out(theme.ignored_entry_fade);
|
||||
}
|
||||
let row_container_style = if show_editor {
|
||||
theme.filename_editor.container
|
||||
} else {
|
||||
|
@ -966,6 +972,7 @@ mod tests {
|
|||
visible_entries_as_strings(&panel, 0..50, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" > b",
|
||||
" > C",
|
||||
|
@ -981,6 +988,7 @@ mod tests {
|
|||
visible_entries_as_strings(&panel, 0..50, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" v b <== selected",
|
||||
" > 3",
|
||||
|
@ -994,7 +1002,7 @@ mod tests {
|
|||
);
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 5..8, cx),
|
||||
visible_entries_as_strings(&panel, 6..9, cx),
|
||||
&[
|
||||
//
|
||||
" > C",
|
||||
|
@ -1058,6 +1066,7 @@ mod tests {
|
|||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1 <== selected",
|
||||
" > .git",
|
||||
" > a",
|
||||
" > b",
|
||||
" > C",
|
||||
|
@ -1076,6 +1085,7 @@ mod tests {
|
|||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" > b",
|
||||
" > C",
|
||||
|
@ -1097,6 +1107,7 @@ mod tests {
|
|||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" > b",
|
||||
" > C",
|
||||
|
@ -1113,6 +1124,7 @@ mod tests {
|
|||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" > b",
|
||||
" > C",
|
||||
|
@ -1127,9 +1139,10 @@ mod tests {
|
|||
select_path(&panel, "root1/b", cx);
|
||||
panel.update(cx, |panel, cx| panel.add_file(&AddFile, cx));
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..9, cx),
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" v b",
|
||||
" > 3",
|
||||
|
@ -1151,9 +1164,10 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..9, cx),
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" v b",
|
||||
" > 3",
|
||||
|
@ -1168,9 +1182,10 @@ mod tests {
|
|||
select_path(&panel, "root1/b/another-filename", cx);
|
||||
panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..9, cx),
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" v b",
|
||||
" > 3",
|
||||
|
@ -1189,9 +1204,10 @@ mod tests {
|
|||
panel.confirm(&Confirm, cx).unwrap()
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..9, cx),
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" v b",
|
||||
" > 3",
|
||||
|
@ -1205,9 +1221,10 @@ mod tests {
|
|||
|
||||
confirm.await.unwrap();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..9, cx),
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" v b",
|
||||
" > 3",
|
||||
|
@ -1221,9 +1238,10 @@ mod tests {
|
|||
|
||||
panel.update(cx, |panel, cx| panel.add_directory(&AddDirectory, cx));
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..9, cx),
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" v b",
|
||||
" > [EDITOR: ''] <== selected",
|
||||
|
@ -1243,9 +1261,10 @@ mod tests {
|
|||
});
|
||||
panel.update(cx, |panel, cx| panel.select_next(&Default::default(), cx));
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..9, cx),
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" v b",
|
||||
" > [PROCESSING: 'new-dir']",
|
||||
|
@ -1259,9 +1278,10 @@ mod tests {
|
|||
|
||||
confirm.await.unwrap();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..9, cx),
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" v b",
|
||||
" > 3 <== selected",
|
||||
|
|
|
@ -223,11 +223,12 @@ pub struct ProjectPanel {
|
|||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub entry: Interactive<ProjectPanelEntry>,
|
||||
pub ignored_entry_fade: f32,
|
||||
pub filename_editor: FieldEditor,
|
||||
pub indent_width: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
#[derive(Clone, Debug, Deserialize, Default)]
|
||||
pub struct ProjectPanelEntry {
|
||||
pub height: f32,
|
||||
#[serde(flatten)]
|
||||
|
|
|
@ -26,6 +26,7 @@ export default function projectPanel(theme: Theme) {
|
|||
text: text(theme, "mono", "active", { size: "sm" }),
|
||||
}
|
||||
},
|
||||
ignoredEntryFade: 0.6,
|
||||
filenameEditor: {
|
||||
background: backgroundColor(theme, 500, "active"),
|
||||
text: text(theme, "mono", "primary", { size: "sm" }),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue