WIP
This commit is contained in:
parent
cbc1d83067
commit
36e6ed3aef
6 changed files with 329 additions and 7 deletions
|
@ -413,7 +413,7 @@ impl MutableAppContext {
|
||||||
windows: HashMap::new(),
|
windows: HashMap::new(),
|
||||||
ref_counts: Arc::new(Mutex::new(RefCounts::default())),
|
ref_counts: Arc::new(Mutex::new(RefCounts::default())),
|
||||||
background: Arc::new(executor::Background::new()),
|
background: Arc::new(executor::Background::new()),
|
||||||
scoped_pool: scoped_pool::Pool::new(num_cpus::get()),
|
thread_pool: scoped_pool::Pool::new(num_cpus::get()),
|
||||||
},
|
},
|
||||||
actions: HashMap::new(),
|
actions: HashMap::new(),
|
||||||
global_actions: HashMap::new(),
|
global_actions: HashMap::new(),
|
||||||
|
@ -1316,7 +1316,7 @@ pub struct AppContext {
|
||||||
windows: HashMap<usize, Window>,
|
windows: HashMap<usize, Window>,
|
||||||
background: Arc<executor::Background>,
|
background: Arc<executor::Background>,
|
||||||
ref_counts: Arc<Mutex<RefCounts>>,
|
ref_counts: Arc<Mutex<RefCounts>>,
|
||||||
scoped_pool: scoped_pool::Pool,
|
thread_pool: scoped_pool::Pool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppContext {
|
impl AppContext {
|
||||||
|
@ -1356,8 +1356,8 @@ impl AppContext {
|
||||||
&self.background
|
&self.background
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scoped_pool(&self) -> &scoped_pool::Pool {
|
pub fn thread_pool(&self) -> &scoped_pool::Pool {
|
||||||
&self.scoped_pool
|
&self.thread_pool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1505,6 +1505,10 @@ impl<'a, T: Entity> ModelContext<'a, T> {
|
||||||
&self.app.ctx.background
|
&self.app.ctx.background
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn thread_pool(&self) -> &scoped_pool::Pool {
|
||||||
|
&self.app.ctx.thread_pool
|
||||||
|
}
|
||||||
|
|
||||||
pub fn halt_stream(&mut self) {
|
pub fn halt_stream(&mut self) {
|
||||||
self.halt_stream = true;
|
self.halt_stream = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -347,7 +347,7 @@ impl FileFinder {
|
||||||
fn spawn_search(&mut self, query: String, ctx: &mut ViewContext<Self>) {
|
fn spawn_search(&mut self, query: String, ctx: &mut ViewContext<Self>) {
|
||||||
let worktrees = self.worktrees(ctx.as_ref());
|
let worktrees = self.worktrees(ctx.as_ref());
|
||||||
let search_id = util::post_inc(&mut self.search_count);
|
let search_id = util::post_inc(&mut self.search_count);
|
||||||
let pool = ctx.as_ref().scoped_pool().clone();
|
let pool = ctx.as_ref().thread_pool().clone();
|
||||||
let task = ctx.background_executor().spawn(async move {
|
let task = ctx.background_executor().spawn(async move {
|
||||||
let matches = match_paths(worktrees.as_slice(), &query, false, false, 100, pool);
|
let matches = match_paths(worktrees.as_slice(), &query, false, false, 100, pool);
|
||||||
(search_id, matches)
|
(search_id, matches)
|
||||||
|
|
|
@ -13,4 +13,5 @@ mod timer;
|
||||||
mod util;
|
mod util;
|
||||||
pub mod watch;
|
pub mod watch;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
|
mod worktree;
|
||||||
mod worktree_old;
|
mod worktree_old;
|
||||||
|
|
|
@ -375,6 +375,12 @@ impl<T: KeyedItem> SumTree<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Item> Default for SumTree<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Node<T: Item> {
|
pub enum Node<T: Item> {
|
||||||
Internal {
|
Internal {
|
||||||
|
|
311
zed/src/worktree.rs
Normal file
311
zed/src/worktree.rs
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
use crate::sum_tree::{self, Edit, SumTree};
|
||||||
|
use gpui::{Entity, ModelContext};
|
||||||
|
use ignore::dir::{Ignore, IgnoreBuilder};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use smol::channel::Sender;
|
||||||
|
use std::{
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
fs, io,
|
||||||
|
ops::AddAssign,
|
||||||
|
os::unix::fs::MetadataExt,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ScanState {
|
||||||
|
Idle,
|
||||||
|
Scanning,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Worktree {
|
||||||
|
path: Arc<Path>,
|
||||||
|
entries: SumTree<Entry>,
|
||||||
|
scanner: BackgroundScanner,
|
||||||
|
scan_state: ScanState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Worktree {
|
||||||
|
fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
|
||||||
|
let path = path.into();
|
||||||
|
let scan_state = smol::channel::unbounded();
|
||||||
|
let scanner = BackgroundScanner::new(path.clone(), scan_state.0);
|
||||||
|
let tree = Self {
|
||||||
|
path,
|
||||||
|
entries: Default::default(),
|
||||||
|
scanner,
|
||||||
|
scan_state: ScanState::Idle,
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let scanner = tree.scanner.clone();
|
||||||
|
std::thread::spawn(move || scanner.run());
|
||||||
|
}
|
||||||
|
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity for Worktree {
|
||||||
|
type Event = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Entry {
|
||||||
|
Dir {
|
||||||
|
parent: Option<u64>,
|
||||||
|
name: Arc<OsStr>,
|
||||||
|
ino: u64,
|
||||||
|
is_symlink: bool,
|
||||||
|
is_ignored: bool,
|
||||||
|
children: Arc<[u64]>,
|
||||||
|
pending: bool,
|
||||||
|
},
|
||||||
|
File {
|
||||||
|
parent: Option<u64>,
|
||||||
|
name: Arc<OsStr>,
|
||||||
|
ino: u64,
|
||||||
|
is_symlink: bool,
|
||||||
|
is_ignored: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
fn ino(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Entry::Dir { ino, .. } => *ino,
|
||||||
|
Entry::File { ino, .. } => *ino,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::Item for Entry {
|
||||||
|
type Summary = EntrySummary;
|
||||||
|
|
||||||
|
fn summary(&self) -> Self::Summary {
|
||||||
|
EntrySummary {
|
||||||
|
max_ino: self.ino(),
|
||||||
|
file_count: if matches!(self, Self::File { .. }) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::KeyedItem for Entry {
|
||||||
|
type Key = u64;
|
||||||
|
|
||||||
|
fn key(&self) -> Self::Key {
|
||||||
|
self.ino()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct EntrySummary {
|
||||||
|
max_ino: u64,
|
||||||
|
file_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AddAssign<&'a EntrySummary> for EntrySummary {
|
||||||
|
fn add_assign(&mut self, rhs: &'a EntrySummary) {
|
||||||
|
self.max_ino = rhs.max_ino;
|
||||||
|
self.file_count += rhs.file_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, EntrySummary> for u64 {
|
||||||
|
fn add_summary(&mut self, summary: &'a EntrySummary) {
|
||||||
|
*self = summary.max_ino;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct BackgroundScanner {
|
||||||
|
path: Arc<Path>,
|
||||||
|
entries: Arc<Mutex<SumTree<Entry>>>,
|
||||||
|
notify: Sender<ScanState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackgroundScanner {
|
||||||
|
fn new(path: Arc<Path>, notify: Sender<ScanState>) -> Self {
|
||||||
|
Self {
|
||||||
|
path,
|
||||||
|
entries: Default::default(),
|
||||||
|
notify,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self) {
|
||||||
|
if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scan_dirs();
|
||||||
|
|
||||||
|
if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Update when dir changes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan_dirs(&self) -> io::Result<()> {
|
||||||
|
let metadata = fs::metadata(&self.path)?;
|
||||||
|
let ino = metadata.ino();
|
||||||
|
let is_symlink = fs::symlink_metadata(&self.path)?.file_type().is_symlink();
|
||||||
|
let name = self.path.file_name().unwrap_or(OsStr::new("/")).into();
|
||||||
|
let relative_path = PathBuf::from(&name);
|
||||||
|
|
||||||
|
let mut ignore = IgnoreBuilder::new()
|
||||||
|
.build()
|
||||||
|
.add_parents(&self.path)
|
||||||
|
.unwrap();
|
||||||
|
if metadata.is_dir() {
|
||||||
|
ignore = ignore.add_child(&self.path).unwrap();
|
||||||
|
}
|
||||||
|
let is_ignored = ignore.matched(&self.path, metadata.is_dir()).is_ignore();
|
||||||
|
|
||||||
|
if metadata.file_type().is_dir() {
|
||||||
|
let is_ignored = is_ignored || name == ".git";
|
||||||
|
|
||||||
|
self.insert_entries(Some(Entry::Dir {
|
||||||
|
parent: None,
|
||||||
|
name,
|
||||||
|
ino,
|
||||||
|
is_symlink,
|
||||||
|
is_ignored,
|
||||||
|
children: Arc::from([]),
|
||||||
|
pending: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
|
||||||
|
tx.send(Ok(ScanJob {
|
||||||
|
ino,
|
||||||
|
path: path.into(),
|
||||||
|
relative_path,
|
||||||
|
ignore: Some(ignore),
|
||||||
|
scan_queue: tx.clone(),
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
Parallel::<io::Result<()>>::new()
|
||||||
|
.each(0..16, |_| {
|
||||||
|
while let Ok(result) = rx.recv() {
|
||||||
|
self.scan_dir(result?)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<io::Result<()>>()?;
|
||||||
|
} else {
|
||||||
|
self.insert_file(None, name, ino, is_symlink, is_ignored, relative_path);
|
||||||
|
}
|
||||||
|
self.0.write().root_ino = Some(ino);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan_dir(&self, to_scan: ScanJob) -> io::Result<()> {
|
||||||
|
let mut new_children = Vec::new();
|
||||||
|
|
||||||
|
for child_entry in fs::read_dir(&to_scan.path)? {
|
||||||
|
let child_entry = child_entry?;
|
||||||
|
let name = child_entry.file_name();
|
||||||
|
let relative_path = to_scan.relative_path.join(&name);
|
||||||
|
let metadata = child_entry.metadata()?;
|
||||||
|
let ino = metadata.ino();
|
||||||
|
let is_symlink = metadata.file_type().is_symlink();
|
||||||
|
|
||||||
|
if metadata.is_dir() {
|
||||||
|
let path = to_scan.path.join(&name);
|
||||||
|
let mut is_ignored = true;
|
||||||
|
let mut ignore = None;
|
||||||
|
|
||||||
|
if let Some(parent_ignore) = to_scan.ignore.as_ref() {
|
||||||
|
let child_ignore = parent_ignore.add_child(&path).unwrap();
|
||||||
|
is_ignored = child_ignore.matched(&path, true).is_ignore() || name == ".git";
|
||||||
|
if !is_ignored {
|
||||||
|
ignore = Some(child_ignore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.insert_entries(
|
||||||
|
Some(Entry::Dir {
|
||||||
|
parent: (),
|
||||||
|
name: (),
|
||||||
|
ino: (),
|
||||||
|
is_symlink: (),
|
||||||
|
is_ignored: (),
|
||||||
|
children: (),
|
||||||
|
pending: (),
|
||||||
|
})
|
||||||
|
.into_iter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.insert_dir(Some(to_scan.ino), name, ino, is_symlink, is_ignored);
|
||||||
|
new_children.push(ino);
|
||||||
|
|
||||||
|
let dirs_to_scan = to_scan.scan_queue.clone();
|
||||||
|
let _ = to_scan.scan_queue.send(Ok(ScanJob {
|
||||||
|
ino,
|
||||||
|
path,
|
||||||
|
relative_path,
|
||||||
|
ignore,
|
||||||
|
scan_queue: dirs_to_scan,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
let is_ignored = to_scan.ignore.as_ref().map_or(true, |i| {
|
||||||
|
i.matched(to_scan.path.join(&name), false).is_ignore()
|
||||||
|
});
|
||||||
|
|
||||||
|
self.insert_file(
|
||||||
|
Some(to_scan.ino),
|
||||||
|
name,
|
||||||
|
ino,
|
||||||
|
is_symlink,
|
||||||
|
is_ignored,
|
||||||
|
relative_path,
|
||||||
|
);
|
||||||
|
new_children.push(ino);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Entry::Dir { children, .. }) = &mut self.0.write().entries.get_mut(&to_scan.ino)
|
||||||
|
{
|
||||||
|
*children = new_children.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_entries(&self, entries: impl IntoIterator<Item = Entry>) {
|
||||||
|
self.entries
|
||||||
|
.lock()
|
||||||
|
.edit(&mut entries.into_iter().map(Edit::Insert).collect::<Vec<_>>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScanJob {
|
||||||
|
ino: u64,
|
||||||
|
path: Arc<Path>,
|
||||||
|
relative_path: PathBuf,
|
||||||
|
ignore: Option<Ignore>,
|
||||||
|
scan_queue: crossbeam_channel::Sender<io::Result<ScanJob>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait UnwrapIgnoreTuple {
|
||||||
|
fn unwrap(self) -> Ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnwrapIgnoreTuple for (Ignore, Option<ignore::Error>) {
|
||||||
|
fn unwrap(self) -> Ignore {
|
||||||
|
if let Some(error) = self.1 {
|
||||||
|
log::error!("error loading gitignore data: {}", error);
|
||||||
|
}
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,7 +74,7 @@ impl Worktree {
|
||||||
|
|
||||||
{
|
{
|
||||||
let tree = tree.clone();
|
let tree = tree.clone();
|
||||||
ctx.as_ref().scoped_pool().spawn(move || {
|
ctx.as_ref().thread_pool().spawn(move || {
|
||||||
if let Err(error) = tree.scan_dirs() {
|
if let Err(error) = tree.scan_dirs() {
|
||||||
log::error!("error scanning worktree: {}", error);
|
log::error!("error scanning worktree: {}", error);
|
||||||
}
|
}
|
||||||
|
@ -725,7 +725,7 @@ mod test {
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
10,
|
10,
|
||||||
ctx.scoped_pool().clone(),
|
ctx.thread_pool().clone(),
|
||||||
)
|
)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|result| tree.entry_path(result.entry_id))
|
.map(|result| tree.entry_path(result.entry_id))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue