Merge pull request #1050 from zed-industries/ignored-files

Show ignored entries in project panel
This commit is contained in:
Antonio Scandurra 2022-05-24 10:57:16 +02:00 committed by GitHub
commit 5c4bd9393f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 178 additions and 30 deletions

View file

@ -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

View file

@ -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());

View file

@ -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",

View file

@ -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)]

View file

@ -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" }),