mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 10:40:54 +00:00
WIP Notifying buffers of head text change
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
parent
759b7f1e07
commit
7e5d49487b
8 changed files with 142 additions and 70 deletions
|
@ -478,13 +478,13 @@ impl Item for Editor {
|
|||
})
|
||||
}
|
||||
|
||||
fn update_git(
|
||||
fn git_diff_recalc(
|
||||
&mut self,
|
||||
_project: ModelHandle<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.buffer().update(cx, |multibuffer, cx| {
|
||||
multibuffer.update_git(cx);
|
||||
multibuffer.git_diff_recalc(cx);
|
||||
});
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
|
|
@ -312,13 +312,13 @@ impl MultiBuffer {
|
|||
self.read(cx).symbols_containing(offset, theme)
|
||||
}
|
||||
|
||||
pub fn update_git(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let buffers = self.buffers.borrow();
|
||||
for buffer_state in buffers.values() {
|
||||
if buffer_state.buffer.read(cx).needs_git_update() {
|
||||
if buffer_state.buffer.read(cx).needs_git_diff_recalc() {
|
||||
buffer_state
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.update_git(cx))
|
||||
.update(cx, |buffer, cx| buffer.git_diff_recalc(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -613,6 +613,7 @@ impl Buffer {
|
|||
cx,
|
||||
);
|
||||
}
|
||||
self.update_git(cx);
|
||||
cx.emit(Event::Reloaded);
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -661,12 +662,19 @@ impl Buffer {
|
|||
self.file = Some(new_file);
|
||||
task
|
||||
}
|
||||
|
||||
pub fn update_git(&mut self, cx: &mut ModelContext<Self>) {
|
||||
//Grab head text
|
||||
|
||||
|
||||
pub fn needs_git_update(&self) -> bool {
|
||||
self.git_diff_recalc(cx);
|
||||
}
|
||||
|
||||
pub fn needs_git_diff_recalc(&self) -> bool {
|
||||
self.git_diff_status.diff.needs_update(self)
|
||||
}
|
||||
|
||||
pub fn update_git(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if self.git_diff_status.update_in_progress {
|
||||
self.git_diff_status.update_requested = true;
|
||||
return;
|
||||
|
@ -692,7 +700,7 @@ impl Buffer {
|
|||
|
||||
this.git_diff_status.update_in_progress = false;
|
||||
if this.git_diff_status.update_requested {
|
||||
this.update_git(cx);
|
||||
this.git_diff_recalc(cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ pub trait Fs: Send + Sync {
|
|||
async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
|
||||
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
|
||||
async fn load(&self, path: &Path) -> Result<String>;
|
||||
async fn load_head_text(&self, path: &Path) -> Option<String>;
|
||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
|
||||
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
|
||||
async fn is_file(&self, path: &Path) -> bool;
|
||||
|
@ -48,7 +47,6 @@ pub trait Fs: Send + Sync {
|
|||
path: &Path,
|
||||
latency: Duration,
|
||||
) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
|
||||
fn open_git_repository(&self, abs_dotgit_path: &Path) -> Option<GitRepository>;
|
||||
fn is_fake(&self) -> bool;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_fake(&self) -> &FakeFs;
|
||||
|
@ -168,38 +166,6 @@ impl Fs for RealFs {
|
|||
Ok(text)
|
||||
}
|
||||
|
||||
async fn load_head_text(&self, path: &Path) -> Option<String> {
|
||||
fn logic(path: &Path) -> Result<Option<String>> {
|
||||
let repo = Repository::open_ext(path, RepositoryOpenFlags::empty(), &[OsStr::new("")])?;
|
||||
assert!(repo.path().ends_with(".git"));
|
||||
let repo_root_path = match repo.path().parent() {
|
||||
Some(root) => root,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let relative_path = path.strip_prefix(repo_root_path)?;
|
||||
let object = repo
|
||||
.head()?
|
||||
.peel_to_tree()?
|
||||
.get_path(relative_path)?
|
||||
.to_object(&repo)?;
|
||||
|
||||
let content = match object.as_blob() {
|
||||
Some(blob) => blob.content().to_owned(),
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let head_text = String::from_utf8(content.to_owned())?;
|
||||
Ok(Some(head_text))
|
||||
}
|
||||
|
||||
match logic(path) {
|
||||
Ok(value) => return value,
|
||||
Err(err) => log::error!("Error loading head text: {:?}", err),
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||
let buffer_size = text.summary().len.min(10 * 1024);
|
||||
let file = smol::fs::File::create(path).await?;
|
||||
|
@ -274,10 +240,6 @@ impl Fs for RealFs {
|
|||
})))
|
||||
}
|
||||
|
||||
fn open_git_repository(&self, abs_dotgit_path: &Path) -> Option<GitRepository> {
|
||||
GitRepository::open(abs_dotgit_path)
|
||||
}
|
||||
|
||||
fn is_fake(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
@ -791,10 +753,6 @@ impl Fs for FakeFs {
|
|||
entry.file_content(&path).cloned()
|
||||
}
|
||||
|
||||
async fn load_head_text(&self, _: &Path) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||
self.simulate_random_delay().await;
|
||||
let path = normalize_path(path);
|
||||
|
@ -893,10 +851,6 @@ impl Fs for FakeFs {
|
|||
}))
|
||||
}
|
||||
|
||||
fn open_git_repository(&self, _: &Path) -> Option<GitRepository> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_fake(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use git2::Repository;
|
||||
use anyhow::Result;
|
||||
use git2::{Repository as LibGitRepository, RepositoryOpenFlags as LibGitRepositoryOpenFlags};
|
||||
use parking_lot::Mutex;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use std::{path::Path, sync::Arc, ffi::OsStr};
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -11,12 +12,12 @@ pub struct GitRepository {
|
|||
// Note: if .git is a file, this points to the folder indicated by the .git file
|
||||
git_dir_path: Arc<Path>,
|
||||
scan_id: usize,
|
||||
libgit_repository: Arc<Mutex<git2::Repository>>,
|
||||
libgit_repository: Arc<Mutex<LibGitRepository>>,
|
||||
}
|
||||
|
||||
impl GitRepository {
|
||||
pub fn open(dotgit_path: &Path) -> Option<GitRepository> {
|
||||
Repository::open(&dotgit_path)
|
||||
LibGitRepository::open(&dotgit_path)
|
||||
.log_err()
|
||||
.and_then(|libgit_repository| {
|
||||
Some(Self {
|
||||
|
@ -60,4 +61,39 @@ impl GitRepository {
|
|||
let mut git2 = self.libgit_repository.lock();
|
||||
f(&mut git2)
|
||||
}
|
||||
|
||||
pub async fn load_head_text(&self, file_path: &Path) -> Option<String> {
|
||||
fn logic(repo: &LibGitRepository, file_path: &Path) -> Result<Option<String>> {
|
||||
let object = repo
|
||||
.head()?
|
||||
.peel_to_tree()?
|
||||
.get_path(file_path)?
|
||||
.to_object(&repo)?;
|
||||
|
||||
let content = match object.as_blob() {
|
||||
Some(blob) => blob.content().to_owned(),
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let head_text = String::from_utf8(content.to_owned())?;
|
||||
Ok(Some(head_text))
|
||||
}
|
||||
|
||||
match logic(&self.libgit_repository.lock(), file_path) {
|
||||
Ok(value) => return value,
|
||||
Err(err) => log::error!("Error loading head text: {:?}", err),
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for GitRepository {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("GitRepository")
|
||||
.field("content_path", &self.content_path)
|
||||
.field("git_dir_path", &self.git_dir_path)
|
||||
.field("scan_id", &self.scan_id)
|
||||
.field("libgit_repository", &"LibGitRepository")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
|||
use clock::ReplicaId;
|
||||
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
||||
use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt};
|
||||
use git_repository::GitRepository;
|
||||
use gpui::{
|
||||
AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
||||
MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle,
|
||||
|
@ -4536,7 +4537,9 @@ impl Project {
|
|||
if worktree.read(cx).is_local() {
|
||||
cx.subscribe(worktree, |this, worktree, event, cx| match event {
|
||||
worktree::Event::UpdatedEntries => this.update_local_worktree_buffers(worktree, cx),
|
||||
worktree::Event::UpdatedGitRepositories(_) => todo!(),
|
||||
worktree::Event::UpdatedGitRepositories(updated_repos) => {
|
||||
this.update_local_worktree_buffers_git_repos(updated_repos, cx)
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
@ -4644,6 +4647,30 @@ impl Project {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_local_worktree_buffers_git_repos(
|
||||
&mut self,
|
||||
updated_repos: &[GitRepository],
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
for (buffer_id, buffer) in &self.opened_buffers {
|
||||
if let Some(buffer) = buffer.upgrade(cx) {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let updated = updated_repos.iter().any(|repo| {
|
||||
buffer
|
||||
.file()
|
||||
.and_then(|file| file.as_local())
|
||||
.map(|file| repo.manages(&file.abs_path(cx)))
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
if updated {
|
||||
buffer.update_git(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
|
||||
let new_active_entry = entry.and_then(|project_path| {
|
||||
let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
|
||||
|
|
|
@ -467,7 +467,7 @@ impl LocalWorktree {
|
|||
.await?;
|
||||
Ok(cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::from_file(0, contents, head_text, Arc::new(file), cx);
|
||||
buffer.update_git(cx);
|
||||
buffer.git_diff_recalc(cx);
|
||||
buffer
|
||||
}))
|
||||
})
|
||||
|
@ -522,16 +522,28 @@ impl LocalWorktree {
|
|||
|
||||
match self.scan_state() {
|
||||
ScanState::Idle => {
|
||||
self.snapshot = self.background_snapshot.lock().clone();
|
||||
let new_snapshot = self.background_snapshot.lock().clone();
|
||||
let updated_repos = self.list_updated_repos(&new_snapshot);
|
||||
self.snapshot = new_snapshot;
|
||||
|
||||
if let Some(share) = self.share.as_mut() {
|
||||
*share.snapshots_tx.borrow_mut() = self.snapshot.clone();
|
||||
}
|
||||
|
||||
cx.emit(Event::UpdatedEntries);
|
||||
|
||||
if !updated_repos.is_empty() {
|
||||
cx.emit(Event::UpdatedGitRepositories(updated_repos));
|
||||
}
|
||||
}
|
||||
|
||||
ScanState::Initializing => {
|
||||
let is_fake_fs = self.fs.is_fake();
|
||||
self.snapshot = self.background_snapshot.lock().clone();
|
||||
|
||||
let new_snapshot = self.background_snapshot.lock().clone();
|
||||
let updated_repos = self.list_updated_repos(&new_snapshot);
|
||||
self.snapshot = new_snapshot;
|
||||
|
||||
self.poll_task = Some(cx.spawn_weak(|this, mut cx| async move {
|
||||
if is_fake_fs {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
@ -543,7 +555,12 @@ impl LocalWorktree {
|
|||
this.update(&mut cx, |this, cx| this.poll_snapshot(cx));
|
||||
}
|
||||
}));
|
||||
|
||||
cx.emit(Event::UpdatedEntries);
|
||||
|
||||
if !updated_repos.is_empty() {
|
||||
cx.emit(Event::UpdatedGitRepositories(updated_repos));
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
|
@ -556,6 +573,34 @@ impl LocalWorktree {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn list_updated_repos(&self, new_snapshot: &LocalSnapshot) -> Vec<GitRepository> {
|
||||
let old_snapshot = &self.snapshot;
|
||||
|
||||
fn diff<'a>(
|
||||
a: &'a LocalSnapshot,
|
||||
b: &'a LocalSnapshot,
|
||||
updated: &mut HashMap<&'a Path, GitRepository>,
|
||||
) {
|
||||
for a_repo in &a.git_repositories {
|
||||
let matched = b.git_repositories.iter().find(|b_repo| {
|
||||
a_repo.git_dir_path() == b_repo.git_dir_path()
|
||||
&& a_repo.scan_id() == b_repo.scan_id()
|
||||
});
|
||||
|
||||
if matched.is_some() {
|
||||
updated.insert(a_repo.git_dir_path(), a_repo.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut updated = HashMap::<&Path, GitRepository>::default();
|
||||
|
||||
diff(old_snapshot, new_snapshot, &mut updated);
|
||||
diff(new_snapshot, old_snapshot, &mut updated);
|
||||
|
||||
updated.into_values().collect()
|
||||
}
|
||||
|
||||
pub fn scan_complete(&self) -> impl Future<Output = ()> {
|
||||
let mut scan_state_rx = self.last_scan_state_rx.clone();
|
||||
async move {
|
||||
|
@ -606,9 +651,11 @@ impl LocalWorktree {
|
|||
files_included,
|
||||
settings::GitFilesIncluded::All | settings::GitFilesIncluded::OnlyTracked
|
||||
) {
|
||||
|
||||
|
||||
let fs = fs.clone();
|
||||
let abs_path = abs_path.clone();
|
||||
let task = async move { fs.load_head_text(&abs_path).await };
|
||||
let opt_future = async move { fs.load_head_text(&abs_path).await };
|
||||
let results = cx.background().spawn(task).await;
|
||||
|
||||
if files_included == settings::GitFilesIncluded::All {
|
||||
|
@ -1495,7 +1542,7 @@ impl LocalSnapshot {
|
|||
.git_repositories
|
||||
.binary_search_by_key(&abs_path.as_path(), |repo| repo.git_dir_path())
|
||||
{
|
||||
if let Some(repository) = fs.open_git_repository(&abs_path) {
|
||||
if let Some(repository) = GitRepository::open(&abs_path) {
|
||||
self.git_repositories.insert(ix, repository);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -317,7 +317,7 @@ pub trait Item: View {
|
|||
project: ModelHandle<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>>;
|
||||
fn update_git(
|
||||
fn git_diff_recalc(
|
||||
&mut self,
|
||||
_project: ModelHandle<Project>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
|
@ -539,7 +539,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
|
|||
) -> Task<Result<()>>;
|
||||
fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
|
||||
-> Task<Result<()>>;
|
||||
fn update_git(
|
||||
fn git_diff_recalc(
|
||||
&self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut MutableAppContext,
|
||||
|
@ -753,7 +753,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
workspace,
|
||||
cx,
|
||||
|project, mut cx| async move {
|
||||
cx.update(|cx| item.update_git(project, cx))
|
||||
cx.update(|cx| item.git_diff_recalc(project, cx))
|
||||
.await
|
||||
.log_err();
|
||||
},
|
||||
|
@ -762,7 +762,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
let project = workspace.project().downgrade();
|
||||
cx.spawn_weak(|_, mut cx| async move {
|
||||
if let Some(project) = project.upgrade(&cx) {
|
||||
cx.update(|cx| item.update_git(project, cx))
|
||||
cx.update(|cx| item.git_diff_recalc(project, cx))
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
|
@ -850,12 +850,12 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
self.update(cx, |item, cx| item.reload(project, cx))
|
||||
}
|
||||
|
||||
fn update_git(
|
||||
fn git_diff_recalc(
|
||||
&self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut MutableAppContext,
|
||||
) -> Task<Result<()>> {
|
||||
self.update(cx, |item, cx| item.update_git(project, cx))
|
||||
self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
|
||||
}
|
||||
|
||||
fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
|
||||
|
|
Loading…
Reference in a new issue