mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
lsp: Watch paths outside of worktrees at language servers request (#17499)
Another stab at https://github.com/zed-industries/zed/pull/17173, this time fixing the segfault found in https://github.com/zed-industries/zed/pull/17206 Release Notes: - Improved language server reliability in multi-worktree projects and monorepo. We now notify the language server more reliably about which files have changed.
This commit is contained in:
parent
938c90fd3b
commit
903f92045a
@ -123,7 +123,7 @@ impl PromptBuilder {
|
|||||||
if params.fs.is_dir(parent_dir).await {
|
if params.fs.is_dir(parent_dir).await {
|
||||||
let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await;
|
let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await;
|
||||||
while let Some(changed_paths) = changes.next().await {
|
while let Some(changed_paths) = changes.next().await {
|
||||||
if changed_paths.iter().any(|p| p == &templates_dir) {
|
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
|
||||||
let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display());
|
let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display());
|
||||||
if let Ok(target) = params.fs.read_link(&templates_dir).await {
|
if let Ok(target) = params.fs.read_link(&templates_dir).await {
|
||||||
log_message.push_str(" -> ");
|
log_message.push_str(" -> ");
|
||||||
@ -162,18 +162,18 @@ impl PromptBuilder {
|
|||||||
let mut combined_changes = futures::stream::select(changes, parent_changes);
|
let mut combined_changes = futures::stream::select(changes, parent_changes);
|
||||||
|
|
||||||
while let Some(changed_paths) = combined_changes.next().await {
|
while let Some(changed_paths) = combined_changes.next().await {
|
||||||
if changed_paths.iter().any(|p| p == &templates_dir) {
|
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
|
||||||
if !params.fs.is_dir(&templates_dir).await {
|
if !params.fs.is_dir(&templates_dir).await {
|
||||||
log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates.");
|
log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates.");
|
||||||
Self::register_built_in_templates(&mut handlebars.lock()).log_err();
|
Self::register_built_in_templates(&mut handlebars.lock()).log_err();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for changed_path in changed_paths {
|
for event in changed_paths {
|
||||||
if changed_path.starts_with(&templates_dir) && changed_path.extension().map_or(false, |ext| ext == "hbs") {
|
if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") {
|
||||||
log::info!("Reloading prompt template override: {}", changed_path.display());
|
log::info!("Reloading prompt template override: {}", event.path.display());
|
||||||
if let Some(content) = params.fs.load(&changed_path).await.log_err() {
|
if let Some(content) = params.fs.load(&event.path).await.log_err() {
|
||||||
let file_name = changed_path.file_stem().unwrap().to_string_lossy();
|
let file_name = event.path.file_stem().unwrap().to_string_lossy();
|
||||||
handlebars.lock().register_template_string(&file_name, content).log_err();
|
handlebars.lock().register_template_string(&file_name, content).log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,9 +369,9 @@ impl ExtensionStore {
|
|||||||
let installed_dir = this.installed_dir.clone();
|
let installed_dir = this.installed_dir.clone();
|
||||||
async move {
|
async move {
|
||||||
let (mut paths, _) = fs.watch(&installed_dir, FS_WATCH_LATENCY).await;
|
let (mut paths, _) = fs.watch(&installed_dir, FS_WATCH_LATENCY).await;
|
||||||
while let Some(paths) = paths.next().await {
|
while let Some(events) = paths.next().await {
|
||||||
for path in paths {
|
for event in events {
|
||||||
let Ok(event_path) = path.strip_prefix(&installed_dir) else {
|
let Ok(event_path) = event.path.strip_prefix(&installed_dir) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,6 +45,25 @@ pub trait Watcher: Send + Sync {
|
|||||||
fn remove(&self, path: &Path) -> Result<()>;
|
fn remove(&self, path: &Path) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum PathEventKind {
|
||||||
|
Removed,
|
||||||
|
Created,
|
||||||
|
Changed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct PathEvent {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub kind: Option<PathEventKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PathEvent> for PathBuf {
|
||||||
|
fn from(event: PathEvent) -> Self {
|
||||||
|
event.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Fs: Send + Sync {
|
pub trait Fs: Send + Sync {
|
||||||
async fn create_dir(&self, path: &Path) -> Result<()>;
|
async fn create_dir(&self, path: &Path) -> Result<()>;
|
||||||
@ -92,7 +111,7 @@ pub trait Fs: Send + Sync {
|
|||||||
path: &Path,
|
path: &Path,
|
||||||
latency: Duration,
|
latency: Duration,
|
||||||
) -> (
|
) -> (
|
||||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||||
Arc<dyn Watcher>,
|
Arc<dyn Watcher>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -469,17 +488,38 @@ impl Fs for RealFs {
|
|||||||
path: &Path,
|
path: &Path,
|
||||||
latency: Duration,
|
latency: Duration,
|
||||||
) -> (
|
) -> (
|
||||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||||
Arc<dyn Watcher>,
|
Arc<dyn Watcher>,
|
||||||
) {
|
) {
|
||||||
use fsevent::EventStream;
|
use fsevent::{EventStream, StreamFlags};
|
||||||
|
|
||||||
let (tx, rx) = smol::channel::unbounded();
|
let (tx, rx) = smol::channel::unbounded();
|
||||||
let (stream, handle) = EventStream::new(&[path], latency);
|
let (stream, handle) = EventStream::new(&[path], latency);
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
stream.run(move |events| {
|
stream.run(move |events| {
|
||||||
smol::block_on(tx.send(events.into_iter().map(|event| event.path).collect()))
|
smol::block_on(
|
||||||
.is_ok()
|
tx.send(
|
||||||
|
events
|
||||||
|
.into_iter()
|
||||||
|
.map(|event| {
|
||||||
|
let kind = if event.flags.contains(StreamFlags::ITEM_REMOVED) {
|
||||||
|
Some(PathEventKind::Removed)
|
||||||
|
} else if event.flags.contains(StreamFlags::ITEM_CREATED) {
|
||||||
|
Some(PathEventKind::Created)
|
||||||
|
} else if event.flags.contains(StreamFlags::ITEM_MODIFIED) {
|
||||||
|
Some(PathEventKind::Changed)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
PathEvent {
|
||||||
|
path: event.path,
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -498,32 +538,46 @@ impl Fs for RealFs {
|
|||||||
path: &Path,
|
path: &Path,
|
||||||
latency: Duration,
|
latency: Duration,
|
||||||
) -> (
|
) -> (
|
||||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||||
Arc<dyn Watcher>,
|
Arc<dyn Watcher>,
|
||||||
) {
|
) {
|
||||||
|
use notify::EventKind;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
let (tx, rx) = smol::channel::unbounded();
|
let (tx, rx) = smol::channel::unbounded();
|
||||||
let pending_paths: Arc<Mutex<Vec<PathBuf>>> = Default::default();
|
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
|
||||||
let root_path = path.to_path_buf();
|
let root_path = path.to_path_buf();
|
||||||
|
|
||||||
watcher::global(|g| {
|
watcher::global(|g| {
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let pending_paths = pending_paths.clone();
|
let pending_paths = pending_paths.clone();
|
||||||
g.add(move |event: ¬ify::Event| {
|
g.add(move |event: ¬ify::Event| {
|
||||||
|
let kind = match event.kind {
|
||||||
|
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||||
|
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||||
|
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
let mut paths = event
|
let mut paths = event
|
||||||
.paths
|
.paths
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|path| path.starts_with(&root_path))
|
.filter_map(|path| {
|
||||||
.cloned()
|
path.starts_with(&root_path).then(|| PathEvent {
|
||||||
|
path: path.clone(),
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if !paths.is_empty() {
|
if !paths.is_empty() {
|
||||||
paths.sort();
|
paths.sort();
|
||||||
let mut pending_paths = pending_paths.lock();
|
let mut pending_paths = pending_paths.lock();
|
||||||
if pending_paths.is_empty() {
|
if pending_paths.is_empty() {
|
||||||
tx.try_send(()).ok();
|
tx.try_send(()).ok();
|
||||||
}
|
}
|
||||||
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, PathBuf::cmp);
|
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, |a, b| {
|
||||||
|
a.path.cmp(&b.path)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -561,10 +615,10 @@ impl Fs for RealFs {
|
|||||||
path: &Path,
|
path: &Path,
|
||||||
_latency: Duration,
|
_latency: Duration,
|
||||||
) -> (
|
) -> (
|
||||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||||
Arc<dyn Watcher>,
|
Arc<dyn Watcher>,
|
||||||
) {
|
) {
|
||||||
use notify::Watcher;
|
use notify::{EventKind, Watcher};
|
||||||
|
|
||||||
let (tx, rx) = smol::channel::unbounded();
|
let (tx, rx) = smol::channel::unbounded();
|
||||||
|
|
||||||
@ -572,7 +626,21 @@ impl Fs for RealFs {
|
|||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
move |event: Result<notify::Event, _>| {
|
move |event: Result<notify::Event, _>| {
|
||||||
if let Some(event) = event.log_err() {
|
if let Some(event) = event.log_err() {
|
||||||
tx.try_send(event.paths).ok();
|
let kind = match event.kind {
|
||||||
|
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||||
|
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||||
|
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.try_send(
|
||||||
|
event
|
||||||
|
.paths
|
||||||
|
.into_iter()
|
||||||
|
.map(|path| PathEvent { path, kind })
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -682,9 +750,9 @@ struct FakeFsState {
|
|||||||
root: Arc<Mutex<FakeFsEntry>>,
|
root: Arc<Mutex<FakeFsEntry>>,
|
||||||
next_inode: u64,
|
next_inode: u64,
|
||||||
next_mtime: SystemTime,
|
next_mtime: SystemTime,
|
||||||
event_txs: Vec<smol::channel::Sender<Vec<PathBuf>>>,
|
event_txs: Vec<smol::channel::Sender<Vec<PathEvent>>>,
|
||||||
events_paused: bool,
|
events_paused: bool,
|
||||||
buffered_events: Vec<PathBuf>,
|
buffered_events: Vec<PathEvent>,
|
||||||
metadata_call_count: usize,
|
metadata_call_count: usize,
|
||||||
read_dir_call_count: usize,
|
read_dir_call_count: usize,
|
||||||
}
|
}
|
||||||
@ -793,11 +861,14 @@ impl FakeFsState {
|
|||||||
|
|
||||||
fn emit_event<I, T>(&mut self, paths: I)
|
fn emit_event<I, T>(&mut self, paths: I)
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = T>,
|
I: IntoIterator<Item = (T, Option<PathEventKind>)>,
|
||||||
T: Into<PathBuf>,
|
T: Into<PathBuf>,
|
||||||
{
|
{
|
||||||
self.buffered_events
|
self.buffered_events
|
||||||
.extend(paths.into_iter().map(Into::into));
|
.extend(paths.into_iter().map(|(path, kind)| PathEvent {
|
||||||
|
path: path.into(),
|
||||||
|
kind,
|
||||||
|
}));
|
||||||
|
|
||||||
if !self.events_paused {
|
if !self.events_paused {
|
||||||
self.flush_events(self.buffered_events.len());
|
self.flush_events(self.buffered_events.len());
|
||||||
@ -872,7 +943,7 @@ impl FakeFs {
|
|||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
state.emit_event([path.to_path_buf()]);
|
state.emit_event([(path.to_path_buf(), None)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
|
pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
|
||||||
@ -895,7 +966,7 @@ impl FakeFs {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
state.emit_event([path]);
|
state.emit_event([(path, None)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_file_internal(&self, path: impl AsRef<Path>, content: Vec<u8>) -> Result<()> {
|
fn write_file_internal(&self, path: impl AsRef<Path>, content: Vec<u8>) -> Result<()> {
|
||||||
@ -910,18 +981,24 @@ impl FakeFs {
|
|||||||
mtime,
|
mtime,
|
||||||
content,
|
content,
|
||||||
}));
|
}));
|
||||||
state.write_path(path, move |entry| {
|
let mut kind = None;
|
||||||
match entry {
|
state.write_path(path, {
|
||||||
btree_map::Entry::Vacant(e) => {
|
let kind = &mut kind;
|
||||||
e.insert(file);
|
move |entry| {
|
||||||
}
|
match entry {
|
||||||
btree_map::Entry::Occupied(mut e) => {
|
btree_map::Entry::Vacant(e) => {
|
||||||
*e.get_mut() = file;
|
*kind = Some(PathEventKind::Created);
|
||||||
|
e.insert(file);
|
||||||
|
}
|
||||||
|
btree_map::Entry::Occupied(mut e) => {
|
||||||
|
*kind = Some(PathEventKind::Changed);
|
||||||
|
*e.get_mut() = file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
})?;
|
})?;
|
||||||
state.emit_event([path]);
|
state.emit_event([(path, kind)]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1030,7 +1107,7 @@ impl FakeFs {
|
|||||||
f(&mut repo_state);
|
f(&mut repo_state);
|
||||||
|
|
||||||
if emit_git_event {
|
if emit_git_event {
|
||||||
state.emit_event([dot_git]);
|
state.emit_event([(dot_git, None)]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("not a directory");
|
panic!("not a directory");
|
||||||
@ -1081,7 +1158,7 @@ impl FakeFs {
|
|||||||
self.state.lock().emit_event(
|
self.state.lock().emit_event(
|
||||||
statuses
|
statuses
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(path, _)| dot_git.parent().unwrap().join(path)),
|
.map(|(path, _)| (dot_git.parent().unwrap().join(path), None)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1251,7 +1328,7 @@ impl Fs for FakeFs {
|
|||||||
state.next_inode += 1;
|
state.next_inode += 1;
|
||||||
state.write_path(&cur_path, |entry| {
|
state.write_path(&cur_path, |entry| {
|
||||||
entry.or_insert_with(|| {
|
entry.or_insert_with(|| {
|
||||||
created_dirs.push(cur_path.clone());
|
created_dirs.push((cur_path.clone(), Some(PathEventKind::Created)));
|
||||||
Arc::new(Mutex::new(FakeFsEntry::Dir {
|
Arc::new(Mutex::new(FakeFsEntry::Dir {
|
||||||
inode,
|
inode,
|
||||||
mtime,
|
mtime,
|
||||||
@ -1263,7 +1340,7 @@ impl Fs for FakeFs {
|
|||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
|
|
||||||
self.state.lock().emit_event(&created_dirs);
|
self.state.lock().emit_event(created_dirs);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1279,10 +1356,12 @@ impl Fs for FakeFs {
|
|||||||
mtime,
|
mtime,
|
||||||
content: Vec::new(),
|
content: Vec::new(),
|
||||||
}));
|
}));
|
||||||
|
let mut kind = Some(PathEventKind::Created);
|
||||||
state.write_path(path, |entry| {
|
state.write_path(path, |entry| {
|
||||||
match entry {
|
match entry {
|
||||||
btree_map::Entry::Occupied(mut e) => {
|
btree_map::Entry::Occupied(mut e) => {
|
||||||
if options.overwrite {
|
if options.overwrite {
|
||||||
|
kind = Some(PathEventKind::Changed);
|
||||||
*e.get_mut() = file;
|
*e.get_mut() = file;
|
||||||
} else if !options.ignore_if_exists {
|
} else if !options.ignore_if_exists {
|
||||||
return Err(anyhow!("path already exists: {}", path.display()));
|
return Err(anyhow!("path already exists: {}", path.display()));
|
||||||
@ -1294,7 +1373,7 @@ impl Fs for FakeFs {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
state.emit_event([path]);
|
state.emit_event([(path, kind)]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1313,7 +1392,8 @@ impl Fs for FakeFs {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
state.emit_event([path]);
|
state.emit_event([(path, None)]);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1388,7 +1468,10 @@ impl Fs for FakeFs {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
state.emit_event(&[old_path, new_path]);
|
state.emit_event([
|
||||||
|
(old_path, Some(PathEventKind::Removed)),
|
||||||
|
(new_path, Some(PathEventKind::Created)),
|
||||||
|
]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1403,9 +1486,11 @@ impl Fs for FakeFs {
|
|||||||
state.next_mtime += Duration::from_nanos(1);
|
state.next_mtime += Duration::from_nanos(1);
|
||||||
let source_entry = state.read_path(&source)?;
|
let source_entry = state.read_path(&source)?;
|
||||||
let content = source_entry.lock().file_content(&source)?.clone();
|
let content = source_entry.lock().file_content(&source)?.clone();
|
||||||
|
let mut kind = Some(PathEventKind::Created);
|
||||||
let entry = state.write_path(&target, |e| match e {
|
let entry = state.write_path(&target, |e| match e {
|
||||||
btree_map::Entry::Occupied(e) => {
|
btree_map::Entry::Occupied(e) => {
|
||||||
if options.overwrite {
|
if options.overwrite {
|
||||||
|
kind = Some(PathEventKind::Changed);
|
||||||
Ok(Some(e.get().clone()))
|
Ok(Some(e.get().clone()))
|
||||||
} else if !options.ignore_if_exists {
|
} else if !options.ignore_if_exists {
|
||||||
return Err(anyhow!("{target:?} already exists"));
|
return Err(anyhow!("{target:?} already exists"));
|
||||||
@ -1425,7 +1510,7 @@ impl Fs for FakeFs {
|
|||||||
if let Some(entry) = entry {
|
if let Some(entry) = entry {
|
||||||
entry.lock().set_file_content(&target, content)?;
|
entry.lock().set_file_content(&target, content)?;
|
||||||
}
|
}
|
||||||
state.emit_event(&[target]);
|
state.emit_event([(target, kind)]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1462,7 +1547,7 @@ impl Fs for FakeFs {
|
|||||||
e.remove();
|
e.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.emit_event(&[path]);
|
state.emit_event([(path, Some(PathEventKind::Removed))]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1491,7 +1576,7 @@ impl Fs for FakeFs {
|
|||||||
e.remove();
|
e.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.emit_event(&[path]);
|
state.emit_event([(path, Some(PathEventKind::Removed))]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1632,7 +1717,7 @@ impl Fs for FakeFs {
|
|||||||
path: &Path,
|
path: &Path,
|
||||||
_: Duration,
|
_: Duration,
|
||||||
) -> (
|
) -> (
|
||||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||||
Arc<dyn Watcher>,
|
Arc<dyn Watcher>,
|
||||||
) {
|
) {
|
||||||
self.simulate_random_delay().await;
|
self.simulate_random_delay().await;
|
||||||
@ -1642,7 +1727,9 @@ impl Fs for FakeFs {
|
|||||||
let executor = self.executor.clone();
|
let executor = self.executor.clone();
|
||||||
(
|
(
|
||||||
Box::pin(futures::StreamExt::filter(rx, move |events| {
|
Box::pin(futures::StreamExt::filter(rx, move |events| {
|
||||||
let result = events.iter().any(|evt_path| evt_path.starts_with(&path));
|
let result = events
|
||||||
|
.iter()
|
||||||
|
.any(|evt_path| evt_path.path.starts_with(&path));
|
||||||
let executor = executor.clone();
|
let executor = executor.clone();
|
||||||
async move {
|
async move {
|
||||||
executor.simulate_random_delay().await;
|
executor.simulate_random_delay().await;
|
||||||
|
@ -22,8 +22,8 @@ use futures::{
|
|||||||
};
|
};
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, AsyncAppContext, Entity, EventEmitter, Model, ModelContext, PromptLevel, Task,
|
AppContext, AsyncAppContext, Context, Entity, EventEmitter, Model, ModelContext, PromptLevel,
|
||||||
WeakModel,
|
Task, WeakModel,
|
||||||
};
|
};
|
||||||
use http_client::HttpClient;
|
use http_client::HttpClient;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -58,7 +58,7 @@ use std::{
|
|||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
iter, mem,
|
iter, mem,
|
||||||
ops::Range,
|
ops::{ControlFlow, Range},
|
||||||
path::{self, Path, PathBuf},
|
path::{self, Path, PathBuf},
|
||||||
process::Stdio,
|
process::Stdio,
|
||||||
str,
|
str,
|
||||||
@ -103,7 +103,7 @@ pub struct LspStore {
|
|||||||
language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
|
language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
|
||||||
language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
|
language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
|
||||||
last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
|
last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
|
||||||
language_server_watched_paths: HashMap<LanguageServerId, HashMap<WorktreeId, GlobSet>>,
|
language_server_watched_paths: HashMap<LanguageServerId, Model<LanguageServerWatchedPaths>>,
|
||||||
language_server_watcher_registrations:
|
language_server_watcher_registrations:
|
||||||
HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
|
HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
|
||||||
active_entry: Option<ProjectEntryId>,
|
active_entry: Option<ProjectEntryId>,
|
||||||
@ -3227,6 +3227,38 @@ impl LspStore {
|
|||||||
.map(|(key, value)| (*key, value))
|
.map(|(key, value)| (*key, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lsp_notify_abs_paths_changed(
|
||||||
|
&mut self,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
changes: Vec<PathEvent>,
|
||||||
|
) {
|
||||||
|
maybe!({
|
||||||
|
let server = self.language_server_for_id(server_id)?;
|
||||||
|
let changes = changes
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|event| {
|
||||||
|
let typ = match event.kind? {
|
||||||
|
PathEventKind::Created => lsp::FileChangeType::CREATED,
|
||||||
|
PathEventKind::Removed => lsp::FileChangeType::DELETED,
|
||||||
|
PathEventKind::Changed => lsp::FileChangeType::CHANGED,
|
||||||
|
};
|
||||||
|
Some(lsp::FileEvent {
|
||||||
|
uri: lsp::Url::from_file_path(&event.path).ok()?,
|
||||||
|
typ,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !changes.is_empty() {
|
||||||
|
server
|
||||||
|
.notify::<lsp::notification::DidChangeWatchedFiles>(
|
||||||
|
lsp::DidChangeWatchedFilesParams { changes },
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn rebuild_watched_paths(
|
fn rebuild_watched_paths(
|
||||||
&mut self,
|
&mut self,
|
||||||
language_server_id: LanguageServerId,
|
language_server_id: LanguageServerId,
|
||||||
@ -3239,62 +3271,176 @@ impl LspStore {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let watched_paths = self
|
let mut worktree_globs = HashMap::default();
|
||||||
.language_server_watched_paths
|
let mut abs_globs = HashMap::default();
|
||||||
.entry(language_server_id)
|
log::trace!(
|
||||||
.or_default();
|
"Processing new watcher paths for language server with id {}",
|
||||||
|
language_server_id
|
||||||
|
);
|
||||||
|
|
||||||
let mut builders = HashMap::default();
|
let worktrees = self
|
||||||
|
.worktree_store
|
||||||
|
.read(cx)
|
||||||
|
.worktrees()
|
||||||
|
.filter_map(|worktree| {
|
||||||
|
self.language_servers_for_worktree(worktree.read(cx).id())
|
||||||
|
.find(|(_, _, server)| server.server_id() == language_server_id)
|
||||||
|
.map(|_| worktree)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
enum PathToWatch {
|
||||||
|
Worktree {
|
||||||
|
literal_prefix: Arc<Path>,
|
||||||
|
pattern: String,
|
||||||
|
},
|
||||||
|
Absolute {
|
||||||
|
path: Arc<Path>,
|
||||||
|
pattern: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
for watcher in watchers.values().flatten() {
|
for watcher in watchers.values().flatten() {
|
||||||
for worktree in self.worktree_store.read(cx).worktrees().collect::<Vec<_>>() {
|
let mut found_host = false;
|
||||||
|
for worktree in &worktrees {
|
||||||
let glob_is_inside_worktree = worktree.update(cx, |tree, _| {
|
let glob_is_inside_worktree = worktree.update(cx, |tree, _| {
|
||||||
if let Some(abs_path) = tree.abs_path().to_str() {
|
if let Some(worktree_root_path) = tree.abs_path().to_str() {
|
||||||
let relative_glob_pattern = match &watcher.glob_pattern {
|
let path_to_watch = match &watcher.glob_pattern {
|
||||||
lsp::GlobPattern::String(s) => Some(
|
lsp::GlobPattern::String(s) => {
|
||||||
s.strip_prefix(abs_path)
|
match s.strip_prefix(worktree_root_path) {
|
||||||
.unwrap_or(s)
|
Some(relative) => {
|
||||||
.strip_prefix(std::path::MAIN_SEPARATOR)
|
let pattern = relative
|
||||||
.unwrap_or(s),
|
.strip_prefix(std::path::MAIN_SEPARATOR)
|
||||||
),
|
.unwrap_or(relative)
|
||||||
|
.to_owned();
|
||||||
|
let literal_prefix = glob_literal_prefix(&pattern);
|
||||||
|
|
||||||
|
let literal_prefix = Arc::from(PathBuf::from(
|
||||||
|
literal_prefix
|
||||||
|
.strip_prefix(std::path::MAIN_SEPARATOR)
|
||||||
|
.unwrap_or(literal_prefix),
|
||||||
|
));
|
||||||
|
PathToWatch::Worktree {
|
||||||
|
literal_prefix,
|
||||||
|
pattern,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let path = glob_literal_prefix(s);
|
||||||
|
let glob = &s[path.len()..];
|
||||||
|
let pattern = glob
|
||||||
|
.strip_prefix(std::path::MAIN_SEPARATOR)
|
||||||
|
.unwrap_or(glob)
|
||||||
|
.to_owned();
|
||||||
|
let path = if Path::new(path).components().next().is_none()
|
||||||
|
{
|
||||||
|
Arc::from(Path::new("/"))
|
||||||
|
} else {
|
||||||
|
PathBuf::from(path).into()
|
||||||
|
};
|
||||||
|
|
||||||
|
PathToWatch::Absolute { path, pattern }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
lsp::GlobPattern::Relative(rp) => {
|
lsp::GlobPattern::Relative(rp) => {
|
||||||
let base_uri = match &rp.base_uri {
|
let Ok(mut base_uri) = match &rp.base_uri {
|
||||||
lsp::OneOf::Left(workspace_folder) => &workspace_folder.uri,
|
lsp::OneOf::Left(workspace_folder) => &workspace_folder.uri,
|
||||||
lsp::OneOf::Right(base_uri) => base_uri,
|
lsp::OneOf::Right(base_uri) => base_uri,
|
||||||
|
}
|
||||||
|
.to_file_path() else {
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
base_uri.to_file_path().ok().and_then(|file_path| {
|
|
||||||
(file_path.to_str() == Some(abs_path))
|
match base_uri.strip_prefix(worktree_root_path) {
|
||||||
.then_some(rp.pattern.as_str())
|
Ok(relative) => {
|
||||||
})
|
let mut literal_prefix = relative.to_owned();
|
||||||
|
literal_prefix.push(glob_literal_prefix(&rp.pattern));
|
||||||
|
|
||||||
|
PathToWatch::Worktree {
|
||||||
|
literal_prefix: literal_prefix.into(),
|
||||||
|
pattern: rp.pattern.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
let path = glob_literal_prefix(&rp.pattern);
|
||||||
|
let glob = &rp.pattern[path.len()..];
|
||||||
|
let pattern = glob
|
||||||
|
.strip_prefix(std::path::MAIN_SEPARATOR)
|
||||||
|
.unwrap_or(glob)
|
||||||
|
.to_owned();
|
||||||
|
base_uri.push(path);
|
||||||
|
|
||||||
|
let path = if base_uri.components().next().is_none() {
|
||||||
|
Arc::from(Path::new("/"))
|
||||||
|
} else {
|
||||||
|
base_uri.into()
|
||||||
|
};
|
||||||
|
PathToWatch::Absolute { path, pattern }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(relative_glob_pattern) = relative_glob_pattern {
|
match path_to_watch {
|
||||||
let literal_prefix = glob_literal_prefix(relative_glob_pattern);
|
PathToWatch::Worktree {
|
||||||
tree.as_local_mut()
|
literal_prefix,
|
||||||
.unwrap()
|
pattern,
|
||||||
.add_path_prefix_to_scan(Path::new(literal_prefix).into());
|
} => {
|
||||||
if let Some(glob) = Glob::new(relative_glob_pattern).log_err() {
|
if let Some((tree, glob)) =
|
||||||
builders
|
tree.as_local_mut().zip(Glob::new(&pattern).log_err())
|
||||||
.entry(tree.id())
|
{
|
||||||
.or_insert_with(GlobSetBuilder::new)
|
tree.add_path_prefix_to_scan(literal_prefix);
|
||||||
.add(glob);
|
worktree_globs
|
||||||
|
.entry(tree.id())
|
||||||
|
.or_insert_with(GlobSetBuilder::new)
|
||||||
|
.add(glob);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PathToWatch::Absolute { path, pattern } => {
|
||||||
|
if let Some(glob) = Glob::new(&pattern).log_err() {
|
||||||
|
abs_globs
|
||||||
|
.entry(path)
|
||||||
|
.or_insert_with(GlobSetBuilder::new)
|
||||||
|
.add(glob);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
});
|
});
|
||||||
if glob_is_inside_worktree {
|
if glob_is_inside_worktree {
|
||||||
break;
|
log::trace!(
|
||||||
|
"Watcher pattern `{}` has been attached to the worktree at `{}`",
|
||||||
|
serde_json::to_string(&watcher.glob_pattern).unwrap(),
|
||||||
|
worktree.read(cx).abs_path().display()
|
||||||
|
);
|
||||||
|
found_host = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !found_host {
|
||||||
|
log::error!(
|
||||||
|
"Watcher pattern `{}` has not been attached to any worktree or absolute path",
|
||||||
|
serde_json::to_string(&watcher.glob_pattern).unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watched_paths.clear();
|
let mut watch_builder = LanguageServerWatchedPathsBuilder::default();
|
||||||
for (worktree_id, builder) in builders {
|
for (worktree_id, builder) in worktree_globs {
|
||||||
if let Ok(globset) = builder.build() {
|
if let Ok(globset) = builder.build() {
|
||||||
watched_paths.insert(worktree_id, globset);
|
watch_builder.watch_worktree(worktree_id, globset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (abs_path, builder) in abs_globs {
|
||||||
|
if let Ok(globset) = builder.build() {
|
||||||
|
watch_builder.watch_abs_path(abs_path, globset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let watcher = watch_builder.build(self.fs.clone(), language_server_id, cx);
|
||||||
|
self.language_server_watched_paths
|
||||||
|
.insert(language_server_id, watcher);
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@ -5427,7 +5573,7 @@ impl LspStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_local_worktree_language_servers(
|
pub(super) fn update_local_worktree_language_servers(
|
||||||
&mut self,
|
&mut self,
|
||||||
worktree_handle: &Model<Worktree>,
|
worktree_handle: &Model<Worktree>,
|
||||||
changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
|
changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
|
||||||
@ -5456,7 +5602,7 @@ impl LspStore {
|
|||||||
if let Some(watched_paths) = self
|
if let Some(watched_paths) = self
|
||||||
.language_server_watched_paths
|
.language_server_watched_paths
|
||||||
.get(&server_id)
|
.get(&server_id)
|
||||||
.and_then(|paths| paths.get(&worktree_id))
|
.and_then(|paths| paths.read(cx).worktree_paths.get(&worktree_id))
|
||||||
{
|
{
|
||||||
let params = lsp::DidChangeWatchedFilesParams {
|
let params = lsp::DidChangeWatchedFilesParams {
|
||||||
changes: changes
|
changes: changes
|
||||||
@ -5991,6 +6137,102 @@ pub enum LanguageServerToQuery {
|
|||||||
Other(LanguageServerId),
|
Other(LanguageServerId),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct LanguageServerWatchedPaths {
|
||||||
|
worktree_paths: HashMap<WorktreeId, GlobSet>,
|
||||||
|
abs_paths: HashMap<Arc<Path>, (GlobSet, Task<()>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct LanguageServerWatchedPathsBuilder {
|
||||||
|
worktree_paths: HashMap<WorktreeId, GlobSet>,
|
||||||
|
abs_paths: HashMap<Arc<Path>, GlobSet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageServerWatchedPathsBuilder {
|
||||||
|
fn watch_worktree(&mut self, worktree_id: WorktreeId, glob_set: GlobSet) {
|
||||||
|
self.worktree_paths.insert(worktree_id, glob_set);
|
||||||
|
}
|
||||||
|
fn watch_abs_path(&mut self, path: Arc<Path>, glob_set: GlobSet) {
|
||||||
|
self.abs_paths.insert(path, glob_set);
|
||||||
|
}
|
||||||
|
fn build(
|
||||||
|
self,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
language_server_id: LanguageServerId,
|
||||||
|
cx: &mut ModelContext<LspStore>,
|
||||||
|
) -> Model<LanguageServerWatchedPaths> {
|
||||||
|
let project = cx.weak_model();
|
||||||
|
|
||||||
|
cx.new_model(|cx| {
|
||||||
|
let this_id = cx.entity_id();
|
||||||
|
const LSP_ABS_PATH_OBSERVE: Duration = Duration::from_millis(100);
|
||||||
|
let abs_paths = self
|
||||||
|
.abs_paths
|
||||||
|
.into_iter()
|
||||||
|
.map(|(abs_path, globset)| {
|
||||||
|
let task = cx.spawn({
|
||||||
|
let abs_path = abs_path.clone();
|
||||||
|
let fs = fs.clone();
|
||||||
|
|
||||||
|
let lsp_store = project.clone();
|
||||||
|
|_, mut cx| async move {
|
||||||
|
maybe!(async move {
|
||||||
|
let mut push_updates =
|
||||||
|
fs.watch(&abs_path, LSP_ABS_PATH_OBSERVE).await;
|
||||||
|
while let Some(update) = push_updates.0.next().await {
|
||||||
|
let action = lsp_store
|
||||||
|
.update(&mut cx, |this, cx| {
|
||||||
|
let Some(watcher) = this
|
||||||
|
.language_server_watched_paths
|
||||||
|
.get(&language_server_id)
|
||||||
|
else {
|
||||||
|
return ControlFlow::Break(());
|
||||||
|
};
|
||||||
|
if watcher.entity_id() != this_id {
|
||||||
|
// This watcher is no longer registered on the project, which means that we should
|
||||||
|
// cease operations.
|
||||||
|
return ControlFlow::Break(());
|
||||||
|
}
|
||||||
|
let (globs, _) = watcher
|
||||||
|
.read(cx)
|
||||||
|
.abs_paths
|
||||||
|
.get(&abs_path)
|
||||||
|
.expect(
|
||||||
|
"Watched abs path is not registered with a watcher",
|
||||||
|
);
|
||||||
|
let matching_entries = update
|
||||||
|
.into_iter()
|
||||||
|
.filter(|event| globs.is_match(&event.path))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
this.lsp_notify_abs_paths_changed(
|
||||||
|
language_server_id,
|
||||||
|
matching_entries,
|
||||||
|
);
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
if action.is_break() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
(abs_path, (globset, task))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
LanguageServerWatchedPaths {
|
||||||
|
worktree_paths: self.worktree_paths,
|
||||||
|
abs_paths,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct LspBufferSnapshot {
|
struct LspBufferSnapshot {
|
||||||
version: i32,
|
version: i32,
|
||||||
snapshot: TextBufferSnapshot,
|
snapshot: TextBufferSnapshot,
|
||||||
@ -6094,7 +6336,9 @@ impl DiagnosticSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn glob_literal_prefix(glob: &str) -> &str {
|
fn glob_literal_prefix(glob: &str) -> &str {
|
||||||
let mut literal_end = 0;
|
let is_absolute = glob.starts_with(path::MAIN_SEPARATOR);
|
||||||
|
|
||||||
|
let mut literal_end = is_absolute as usize;
|
||||||
for (i, part) in glob.split(path::MAIN_SEPARATOR).enumerate() {
|
for (i, part) in glob.split(path::MAIN_SEPARATOR).enumerate() {
|
||||||
if part.contains(['*', '?', '{', '}']) {
|
if part.contains(['*', '?', '{', '}']) {
|
||||||
break;
|
break;
|
||||||
@ -6106,6 +6350,7 @@ fn glob_literal_prefix(glob: &str) -> &str {
|
|||||||
literal_end += part.len();
|
literal_end += part.len();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let literal_end = literal_end.min(glob.len());
|
||||||
&glob[..literal_end]
|
&glob[..literal_end]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,12 @@ impl SnippetProvider {
|
|||||||
|
|
||||||
let (mut entries, _) = watcher.await;
|
let (mut entries, _) = watcher.await;
|
||||||
while let Some(entries) = entries.next().await {
|
while let Some(entries) = entries.next().await {
|
||||||
process_updates(this.clone(), entries, cx.clone()).await?;
|
process_updates(
|
||||||
|
this.clone(),
|
||||||
|
entries.into_iter().map(|event| event.path).collect(),
|
||||||
|
cx.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
@ -7,7 +7,7 @@ use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
|
|||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{HashMap, HashSet, VecDeque};
|
use collections::{HashMap, HashSet, VecDeque};
|
||||||
use fs::{copy_recursive, Fs, RemoveOptions, Watcher};
|
use fs::{copy_recursive, Fs, PathEvent, RemoveOptions, Watcher};
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::{
|
channel::{
|
||||||
mpsc::{self, UnboundedSender},
|
mpsc::{self, UnboundedSender},
|
||||||
@ -1048,7 +1048,11 @@ impl LocalWorktree {
|
|||||||
watcher,
|
watcher,
|
||||||
};
|
};
|
||||||
|
|
||||||
scanner.run(events).await;
|
scanner
|
||||||
|
.run(Box::pin(
|
||||||
|
events.map(|events| events.into_iter().map(Into::into).collect()),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let scan_state_updater = cx.spawn(|this, mut cx| async move {
|
let scan_state_updater = cx.spawn(|this, mut cx| async move {
|
||||||
@ -3512,7 +3516,7 @@ enum BackgroundScannerPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BackgroundScanner {
|
impl BackgroundScanner {
|
||||||
async fn run(&mut self, mut fs_events_rx: Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>) {
|
async fn run(&mut self, mut fs_events_rx: Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>) {
|
||||||
use futures::FutureExt as _;
|
use futures::FutureExt as _;
|
||||||
|
|
||||||
// If the worktree root does not contain a git repository, then find
|
// If the worktree root does not contain a git repository, then find
|
||||||
@ -3593,7 +3597,8 @@ impl BackgroundScanner {
|
|||||||
while let Poll::Ready(Some(more_paths)) = futures::poll!(fs_events_rx.next()) {
|
while let Poll::Ready(Some(more_paths)) = futures::poll!(fs_events_rx.next()) {
|
||||||
paths.extend(more_paths);
|
paths.extend(more_paths);
|
||||||
}
|
}
|
||||||
self.process_events(paths).await;
|
self.process_events(paths.into_iter().map(Into::into).collect())
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue processing events until the worktree is dropped.
|
// Continue processing events until the worktree is dropped.
|
||||||
@ -3634,7 +3639,7 @@ impl BackgroundScanner {
|
|||||||
while let Poll::Ready(Some(more_paths)) = futures::poll!(fs_events_rx.next()) {
|
while let Poll::Ready(Some(more_paths)) = futures::poll!(fs_events_rx.next()) {
|
||||||
paths.extend(more_paths);
|
paths.extend(more_paths);
|
||||||
}
|
}
|
||||||
self.process_events(paths.clone()).await;
|
self.process_events(paths.into_iter().map(Into::into).collect()).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1153,13 +1153,13 @@ fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
while let Some(paths) = events.next().await {
|
while let Some(paths) = events.next().await {
|
||||||
for path in paths {
|
for event in paths {
|
||||||
if fs.metadata(&path).await.ok().flatten().is_some() {
|
if fs.metadata(&event.path).await.ok().flatten().is_some() {
|
||||||
if let Some(theme_registry) =
|
if let Some(theme_registry) =
|
||||||
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
|
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
|
||||||
{
|
{
|
||||||
if let Some(()) = theme_registry
|
if let Some(()) = theme_registry
|
||||||
.load_user_theme(&path, fs.clone())
|
.load_user_theme(&event.path, fs.clone())
|
||||||
.await
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
{
|
{
|
||||||
@ -1188,8 +1188,10 @@ fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &m
|
|||||||
cx.spawn(|_| async move {
|
cx.spawn(|_| async move {
|
||||||
let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await;
|
let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await;
|
||||||
while let Some(event) = events.next().await {
|
while let Some(event) = events.next().await {
|
||||||
let has_language_file = event.iter().any(|path| {
|
let has_language_file = event.iter().any(|event| {
|
||||||
path.extension()
|
event
|
||||||
|
.path
|
||||||
|
.extension()
|
||||||
.map(|ext| ext.to_string_lossy().as_ref() == "scm")
|
.map(|ext| ext.to_string_lossy().as_ref() == "scm")
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user