WIP Notifying buffers of head text change

Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Julia 2022-09-27 20:06:18 -04:00
parent 759b7f1e07
commit 7e5d49487b
8 changed files with 142 additions and 70 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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