Detailed changes
@@ -2245,6 +2245,20 @@ impl<T: Entity> WeakModelHandle<T> {
}
}
+impl<T> Hash for WeakModelHandle<T> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.model_id.hash(state)
+ }
+}
+
+impl<T> PartialEq for WeakModelHandle<T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.model_id == other.model_id
+ }
+}
+
+impl<T> Eq for WeakModelHandle<T> {}
+
impl<T> Clone for WeakModelHandle<T> {
fn clone(&self) -> Self {
Self {
@@ -45,6 +45,7 @@ message OpenWorktree {
message OpenWorktreeResponse {
Worktree worktree = 1;
+ uint32 replica_id = 2;
}
message AddGuest {
@@ -32,7 +32,7 @@ use std::{
ops::{Deref, DerefMut, Range},
str,
sync::Arc,
- time::{Duration, Instant},
+ time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
const UNDO_GROUP_INTERVAL: Duration = Duration::from_millis(300);
@@ -110,7 +110,7 @@ pub struct Buffer {
deleted_text: Rope,
pub version: time::Global,
saved_version: time::Global,
- saved_mtime: Duration,
+ saved_mtime: SystemTime,
last_edit: time::Local,
undo_map: UndoMap,
history: History,
@@ -438,7 +438,7 @@ impl Buffer {
if let Some(file) = file.as_ref() {
saved_mtime = file.read(cx).mtime(cx.as_ref());
} else {
- saved_mtime = Duration::ZERO;
+ saved_mtime = UNIX_EPOCH;
}
let mut fragments = SumTree::new();
@@ -466,7 +466,7 @@ impl Buffer {
last_edit: time::Local::default(),
undo_map: Default::default(),
history,
- file: None,
+ file,
syntax_tree: Mutex::new(None),
is_parsing: false,
language,
@@ -479,7 +479,6 @@ impl Buffer {
local_clock: time::Local::new(replica_id),
lamport_clock: time::Lamport::new(replica_id),
};
- result.set_file(file, cx);
result.reparse(cx);
result
}
@@ -526,7 +525,7 @@ impl Buffer {
cx: &mut ModelContext<Self>,
) {
if file.is_some() {
- self.set_file(file, cx);
+ self.file = file;
}
if let Some(file) = &self.file {
self.saved_mtime = file.read(cx).mtime(cx.as_ref());
@@ -535,42 +534,32 @@ impl Buffer {
cx.emit(Event::Saved);
}
- fn set_file(&mut self, file: Option<ModelHandle<File>>, cx: &mut ModelContext<Self>) {
- self.file = file;
- if let Some(file) = &self.file {
- cx.observe(file, |this, file, cx| {
- let version = this.version.clone();
- if this.version == this.saved_version {
- if file.read(cx).is_deleted(cx.as_ref()) {
- cx.emit(Event::Dirtied);
- } else {
- cx.spawn(|this, mut cx| async move {
- let (current_version, history) = this.read_with(&cx, |this, cx| {
- (
- this.version.clone(),
- file.read(cx).load_history(cx.as_ref()),
- )
- });
- if let (Ok(history), true) = (history.await, current_version == version)
- {
- let diff = this
- .read_with(&cx, |this, cx| this.diff(history.base_text, cx))
- .await;
- this.update(&mut cx, |this, cx| {
- if let Some(_ops) = this.set_text_via_diff(diff, cx) {
- this.saved_version = this.version.clone();
- this.saved_mtime = file.read(cx).mtime(cx.as_ref());
- cx.emit(Event::Reloaded);
- }
- });
- }
- })
- .detach();
+ pub fn file_was_deleted(&mut self, cx: &mut ModelContext<Self>) {
+ cx.emit(Event::Dirtied);
+ }
+
+ pub fn file_was_modified(
+ &mut self,
+ new_text: String,
+ mtime: SystemTime,
+ cx: &mut ModelContext<Self>,
+ ) {
+ if self.version == self.saved_version {
+ cx.spawn(|this, mut cx| async move {
+ let diff = this
+ .read_with(&cx, |this, cx| this.diff(new_text.into(), cx))
+ .await;
+ this.update(&mut cx, |this, cx| {
+ if let Some(_ops) = this.set_text_via_diff(diff, cx) {
+ this.saved_version = this.version.clone();
+ this.saved_mtime = mtime;
+ cx.emit(Event::Reloaded);
}
- }
- cx.emit(Event::FileHandleChanged);
- });
+ });
+ })
+ .detach();
}
+ cx.emit(Event::FileHandleChanged);
}
pub fn syntax_tree(&self) -> Option<Tree> {
@@ -480,7 +480,6 @@ mod tests {
let app_state = cx.read(build_app_state);
let (window_id, workspace) = cx.add_window(|cx| {
let mut workspace = Workspace::new(
- 0,
app_state.settings,
app_state.language_registry,
app_state.rpc,
@@ -553,7 +552,6 @@ mod tests {
let app_state = cx.read(build_app_state);
let (_, workspace) = cx.add_window(|cx| {
let mut workspace = Workspace::new(
- 0,
app_state.settings.clone(),
app_state.language_registry.clone(),
app_state.rpc.clone(),
@@ -617,7 +615,6 @@ mod tests {
let app_state = cx.read(build_app_state);
let (_, workspace) = cx.add_window(|cx| {
let mut workspace = Workspace::new(
- 0,
app_state.settings.clone(),
app_state.language_registry.clone(),
app_state.rpc.clone(),
@@ -669,7 +666,6 @@ mod tests {
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(
- 0,
app_state.settings.clone(),
app_state.language_registry.clone(),
app_state.rpc.clone(),
@@ -11,11 +11,12 @@ use std::collections::HashMap;
use std::time::Duration;
use std::{convert::TryFrom, future::Future, sync::Arc};
use surf::Url;
-use zed_rpc::proto::EnvelopedMessage;
-use zed_rpc::{proto::RequestMessage, rest, Peer, TypedEnvelope};
-use zed_rpc::{PeerId, Receipt};
+use zed_rpc::{
+ proto::{EnvelopedMessage, RequestMessage},
+ rest, Peer, Receipt, TypedEnvelope,
+};
-pub use zed_rpc::{proto, ConnectionId};
+pub use zed_rpc::{proto, ConnectionId, PeerId};
lazy_static! {
static ref ZED_SERVER_URL: String =
@@ -96,7 +96,6 @@ fn open_paths(params: &OpenParams, cx: &mut MutableAppContext) {
// Add a new workspace if necessary
cx.add_window(|cx| {
let mut view = Workspace::new(
- 0,
params.app_state.settings.clone(),
params.app_state.language_registry.clone(),
params.app_state.rpc.clone(),
@@ -394,7 +393,6 @@ pub struct Workspace {
center: PaneGroup,
panes: Vec<ViewHandle<Pane>>,
active_pane: ViewHandle<Pane>,
- replica_id: ReplicaId,
worktrees: HashSet<ModelHandle<Worktree>>,
items: Vec<Box<dyn WeakItemHandle>>,
loading_items: HashMap<
@@ -405,7 +403,6 @@ pub struct Workspace {
impl Workspace {
pub fn new(
- replica_id: ReplicaId,
settings: watch::Receiver<Settings>,
language_registry: Arc<LanguageRegistry>,
rpc: rpc::Client,
@@ -426,7 +423,6 @@ impl Workspace {
settings,
language_registry,
rpc,
- replica_id,
worktrees: Default::default(),
items: Default::default(),
loading_items: Default::default(),
@@ -557,7 +553,7 @@ impl Workspace {
}
pub fn open_new_file(&mut self, _: &(), cx: &mut ViewContext<Self>) {
- let buffer = cx.add_model(|cx| Buffer::new(self.replica_id, "", cx));
+ let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
let buffer_view =
cx.add_view(|cx| Editor::for_buffer(buffer.clone(), self.settings.clone(), cx));
self.items.push(ItemHandle::downgrade(&buffer));
@@ -615,32 +611,23 @@ impl Workspace {
}
};
- let file = worktree.file(path.clone(), cx.as_mut());
if let Entry::Vacant(entry) = self.loading_items.entry(entry.clone()) {
let (mut tx, rx) = postage::watch::channel();
entry.insert(rx);
- let replica_id = self.replica_id;
let language_registry = self.language_registry.clone();
cx.as_mut()
.spawn(|mut cx| async move {
- let buffer = async move {
- let history = cx.read(|cx| file.read(cx).load_history(cx));
- let history = cx.background_executor().spawn(history).await?;
- let buffer = cx.add_model(|cx| {
- let language = language_registry.select_language(path);
- Buffer::from_history(
- replica_id,
- history,
- Some(file),
- language.cloned(),
- cx,
- )
- });
- Ok(Box::new(buffer) as Box<dyn ItemHandle>)
- }
- .await;
- *tx.borrow_mut() = Some(buffer.map_err(Arc::new));
+ let buffer = worktree
+ .update(&mut cx, |worktree, cx| {
+ worktree.open_buffer(path.as_ref(), language_registry, cx)
+ })
+ .await;
+ *tx.borrow_mut() = Some(
+ buffer
+ .map(|buffer| Box::new(buffer) as Box<dyn ItemHandle>)
+ .map_err(Arc::new),
+ );
})
.detach();
}
@@ -809,11 +796,12 @@ impl Workspace {
let worktree = open_worktree_response
.worktree
.ok_or_else(|| anyhow!("empty worktree"))?;
+ let replica_id = open_worktree_response.replica_id as ReplicaId;
let worktree_id = worktree_id.try_into().unwrap();
this.update(&mut cx, |workspace, cx| {
let worktree = cx.add_model(|cx| {
- Worktree::remote(worktree_id, worktree, rpc, connection_id, cx)
+ Worktree::remote(worktree_id, worktree, rpc, connection_id, replica_id, cx)
});
cx.observe_model(&worktree, |_, _, cx| cx.notify());
workspace.worktrees.insert(worktree);
@@ -1047,7 +1035,6 @@ mod tests {
let (_, workspace) = cx.add_window(|cx| {
let mut workspace = Workspace::new(
- 0,
app_state.settings,
app_state.language_registry,
app_state.rpc,
@@ -1156,7 +1143,6 @@ mod tests {
let app_state = cx.read(build_app_state);
let (_, workspace) = cx.add_window(|cx| {
let mut workspace = Workspace::new(
- 0,
app_state.settings,
app_state.language_registry,
app_state.rpc,
@@ -1230,7 +1216,6 @@ mod tests {
let app_state = cx.read(build_app_state);
let (window_id, workspace) = cx.add_window(|cx| {
let mut workspace = Workspace::new(
- 0,
app_state.settings,
app_state.language_registry,
app_state.rpc,
@@ -1279,7 +1264,6 @@ mod tests {
let app_state = cx.read(build_app_state);
let (_, workspace) = cx.add_window(|cx| {
let mut workspace = Workspace::new(
- 0,
app_state.settings,
app_state.language_registry,
app_state.rpc,
@@ -1385,7 +1369,6 @@ mod tests {
let app_state = cx.read(build_app_state);
let (window_id, workspace) = cx.add_window(|cx| {
let mut workspace = Workspace::new(
- 0,
app_state.settings,
app_state.language_registry,
app_state.rpc,
@@ -4,15 +4,20 @@ mod ignore;
use self::{char_bag::CharBag, ignore::IgnoreStack};
use crate::{
- editor::{History, Rope},
- rpc::{self, proto, ConnectionId},
+ editor::{Buffer, History, Rope},
+ language::LanguageRegistry,
+ rpc::{self, proto, ConnectionId, PeerId},
sum_tree::{self, Cursor, Edit, SumTree},
+ time::ReplicaId,
util::Bias,
};
use ::ignore::gitignore::Gitignore;
use anyhow::{Context, Result};
pub use fuzzy::{match_paths, PathMatch};
-use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
+use gpui::{
+ scoped_pool, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
+ WeakModelHandle,
+};
use lazy_static::lazy_static;
use parking_lot::Mutex;
use postage::{
@@ -30,7 +35,7 @@ use std::{
ops::Deref,
os::unix::fs::MetadataExt,
path::{Path, PathBuf},
- sync::{atomic::AtomicU64, Arc},
+ sync::Arc,
time::{Duration, SystemTime, UNIX_EPOCH},
};
@@ -64,9 +69,17 @@ impl Worktree {
worktree: proto::Worktree,
rpc: rpc::Client,
connection_id: ConnectionId,
+ replica_id: ReplicaId,
cx: &mut ModelContext<Worktree>,
) -> Self {
- Worktree::Remote(RemoteWorktree::new(id, worktree, rpc, connection_id, cx))
+ Worktree::Remote(RemoteWorktree::new(
+ id,
+ worktree,
+ rpc,
+ connection_id,
+ replica_id,
+ cx,
+ ))
}
pub fn as_local(&self) -> Option<&LocalWorktree> {
@@ -92,6 +105,18 @@ impl Worktree {
}
}
+ pub fn open_buffer(
+ &mut self,
+ path: &Path,
+ language_registry: Arc<LanguageRegistry>,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<ModelHandle<Buffer>>> {
+ match self {
+ Worktree::Local(worktree) => worktree.open_buffer(path, language_registry, cx),
+ Worktree::Remote(_) => todo!(),
+ }
+ }
+
pub fn save(
&self,
path: &Path,
@@ -119,11 +144,11 @@ impl Deref for Worktree {
pub struct LocalWorktree {
snapshot: Snapshot,
background_snapshot: Arc<Mutex<Snapshot>>,
- next_handle_id: AtomicU64,
scan_state: (watch::Sender<ScanState>, watch::Receiver<ScanState>),
_event_stream_handle: fsevent::Handle,
poll_scheduled: bool,
rpc: Option<rpc::Client>,
+ open_buffers: HashSet<WeakModelHandle<Buffer>>,
}
impl LocalWorktree {
@@ -147,7 +172,7 @@ impl LocalWorktree {
let tree = Self {
snapshot,
background_snapshot: background_snapshot.clone(),
- next_handle_id: Default::default(),
+ open_buffers: Default::default(),
scan_state: watch::channel_with(ScanState::Scanning),
_event_stream_handle: event_stream_handle,
poll_scheduled: false,
@@ -189,6 +214,47 @@ impl LocalWorktree {
tree
}
+ pub fn open_buffer(
+ &mut self,
+ path: &Path,
+ language_registry: Arc<LanguageRegistry>,
+ cx: &mut ModelContext<Worktree>,
+ ) -> Task<Result<ModelHandle<Buffer>>> {
+ let handle = cx.handle();
+
+ // If there is already a buffer for the given path, then return it.
+ let mut existing_buffer = None;
+ self.open_buffers.retain(|buffer| {
+ if let Some(buffer) = buffer.upgrade(cx.as_ref()) {
+ if let Some(file) = buffer.read(cx.as_ref()).file() {
+ let file = file.read(cx.as_ref());
+ if file.worktree_id() == handle.id() && file.path.as_ref() == path {
+ existing_buffer = Some(buffer);
+ }
+ }
+ true
+ } else {
+ false
+ }
+ });
+
+ let path = Arc::from(path);
+ let contents = self.load(&path, cx.as_ref());
+ cx.spawn(|this, mut cx| async move {
+ let contents = contents.await?;
+ let language = language_registry.select_language(&path).cloned();
+ let file = cx.add_model(|cx| File::new(handle, path.into(), cx));
+ let buffer = cx.add_model(|cx| {
+ Buffer::from_history(0, History::new(contents.into()), Some(file), language, cx)
+ });
+ this.update(&mut cx, |this, _| {
+ let this = this.as_local_mut().unwrap();
+ this.open_buffers.insert(buffer.downgrade());
+ });
+ Ok(buffer)
+ })
+ }
+
pub fn scan_complete(&self) -> impl Future<Output = ()> {
let mut scan_state_rx = self.scan_state.1.clone();
async move {
@@ -200,13 +266,61 @@ impl LocalWorktree {
}
fn observe_scan_state(&mut self, mut scan_state: ScanState, cx: &mut ModelContext<Worktree>) {
- if let ScanState::Idle(diff) = &mut scan_state {
- if let Some(diff) = diff.take() {
- cx.emit(diff);
- }
- }
- let _ = self.scan_state.0.blocking_send(scan_state);
+ let diff = if let ScanState::Idle(diff) = &mut scan_state {
+ diff.take()
+ } else {
+ None
+ };
+
+ self.scan_state.0.blocking_send(scan_state).ok();
self.poll_snapshot(cx);
+
+ if let Some(diff) = diff {
+ let handle = cx.handle();
+ self.open_buffers.retain(|buffer| {
+ if let Some(buffer) = buffer.upgrade(cx.as_ref()) {
+ buffer.update(cx, |buffer, cx| {
+ let handle = handle.clone();
+ if let Some(file) = buffer.file() {
+ let path = file.read(cx.as_ref()).path.clone();
+ if diff.added.contains(&path) {
+ cx.notify();
+ }
+ // Notify any buffers whose files were deleted.
+ else if diff.removed.contains(&path) {
+ buffer.file_was_deleted(cx);
+ }
+ // Notify any buffers whose files were modified.
+ else if diff.modified.contains(&path) {
+ cx.spawn(|buffer, mut cx| async move {
+ let new_contents = handle
+ .read_with(&cx, |this, cx| {
+ let this = this.as_local().unwrap();
+ this.load(&path, cx)
+ })
+ .await?;
+ let mtime = handle.read_with(&cx, |this, _| {
+ let this = this.as_local().unwrap();
+ this.entry_for_path(&path).map(|entry| entry.mtime)
+ });
+ if let Some(mtime) = mtime {
+ buffer.update(&mut cx, |buffer, cx| {
+ buffer.file_was_modified(new_contents, mtime, cx)
+ });
+ }
+ Result::<_, anyhow::Error>::Ok(())
+ })
+ .detach();
+ }
+ }
+ });
+ true
+ } else {
+ false
+ }
+ });
+ cx.emit(diff);
+ }
}
fn poll_snapshot(&mut self, cx: &mut ModelContext<Worktree>) {
@@ -255,6 +369,16 @@ impl LocalWorktree {
}
}
+ fn load(&self, path: &Path, cx: &AppContext) -> Task<Result<String>> {
+ let abs_path = self.absolutize(path);
+ cx.background_executor().spawn(async move {
+ let mut file = fs::File::open(&abs_path)?;
+ let mut contents = String::new();
+ file.read_to_string(&mut contents)?;
+ Result::<_, anyhow::Error>::Ok(contents)
+ })
+ }
+
pub fn save(&self, path: &Path, content: Rope, cx: &AppContext) -> Task<Result<()>> {
let path = path.to_path_buf();
let abs_path = self.absolutize(&path);
@@ -340,6 +464,7 @@ pub struct RemoteWorktree {
snapshot: Snapshot,
rpc: rpc::Client,
connection_id: ConnectionId,
+ replica_id: ReplicaId,
}
impl RemoteWorktree {
@@ -348,6 +473,7 @@ impl RemoteWorktree {
worktree: proto::Worktree,
rpc: rpc::Client,
connection_id: ConnectionId,
+ replica_id: ReplicaId,
cx: &mut ModelContext<Worktree>,
) -> Self {
let root_char_bag: CharBag = worktree
@@ -394,6 +520,7 @@ impl RemoteWorktree {
snapshot,
rpc,
connection_id,
+ replica_id,
}
}
}
@@ -699,44 +826,11 @@ impl File {
!self.is_deleted(cx)
}
- pub fn mtime(&self, cx: &AppContext) -> Duration {
+ pub fn mtime(&self, cx: &AppContext) -> SystemTime {
let snapshot = self.worktree.read(cx).snapshot();
snapshot
.entry_for_path(&self.path)
- .map_or(Duration::ZERO, |entry| {
- entry.mtime.duration_since(UNIX_EPOCH).unwrap()
- })
- }
-
- pub fn load_history(&self, cx: &AppContext) -> Task<Result<History>> {
- match self.worktree.read(cx) {
- Worktree::Local(worktree) => {
- let abs_path = worktree.absolutize(&self.path);
- cx.background_executor().spawn(async move {
- let mut file = fs::File::open(&abs_path)?;
- let mut base_text = String::new();
- file.read_to_string(&mut base_text)?;
- Ok(History::new(Arc::from(base_text)))
- })
- }
- Worktree::Remote(worktree) => {
- todo!()
- // let state = self.state.lock();
- // let id = state.id;
- // let worktree_id = worktree.remote_id as u64;
- // let (connection_id, rpc) = state.rpc.clone().unwrap();
- // cx.background_executor().spawn(async move {
- // let response = rpc
- // .request(connection_id, proto::OpenBuffer { worktree_id, id })
- // .await?;
- // let buffer = response
- // .buffer
- // .ok_or_else(|| anyhow!("buffer must be present"))?;
- // let history = History::new(buffer.content.into());
- // Ok(history)
- // })
- }
- }
+ .map_or(UNIX_EPOCH, |entry| entry.mtime)
}
pub fn save(&self, content: Rope, cx: &AppContext) -> impl Future<Output = Result<()>> {
@@ -1579,26 +1673,21 @@ mod tests {
}));
let tree = cx.add_model(|cx| Worktree::local(dir.path(), cx));
- cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
+ tree.read_with(&cx, |tree, _| tree.as_local().unwrap().scan_complete())
.await;
- cx.read(|cx| assert_eq!(tree.read(cx).file_count(), 1));
-
- let buffer = cx.add_model(|cx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), cx));
-
- let path = tree.update(&mut cx, |tree, cx| {
- let path = tree.files(0).next().unwrap().path().clone();
- assert_eq!(path.file_name().unwrap(), "file1");
- smol::block_on(tree.save(&path, buffer.read(cx).snapshot().text(), cx.as_ref()))
- .unwrap();
- path
+ let path = tree.read_with(&cx, |tree, _| {
+ assert_eq!(tree.file_count(), 1);
+ tree.files(0).next().unwrap().path().clone()
});
-
- let history = cx
- .update(|cx| tree.file(&path, cx).read(cx).load_history(cx.as_ref()))
- .await
- .unwrap();
- cx.read(|cx| {
- assert_eq!(history.base_text.as_ref(), buffer.read(cx).text());
+ assert_eq!(path.file_name().unwrap(), "file1");
+
+ tree.update(&mut cx, |tree, cx| {
+ let buffer =
+ cx.add_model(|cx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), cx));
+ let text = buffer.read(cx).snapshot().text();
+ smol::block_on(tree.save(&path, text, cx.as_ref())).unwrap();
+ let new_contents = fs::read_to_string(dir.path().join(path)).unwrap();
+ assert_eq!(new_contents, buffer.read(cx).text());
});
}
@@ -1607,8 +1696,9 @@ mod tests {
let dir = temp_tree(json!({
"file1": "the old contents",
}));
+ let file_path = dir.path().join("file1");
- let tree = cx.add_model(|cx| Worktree::local(dir.path().join("file1"), cx));
+ let tree = cx.add_model(|cx| Worktree::local(file_path.clone(), cx));
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
cx.read(|cx| assert_eq!(tree.read(cx).file_count(), 1));
@@ -1616,16 +1706,13 @@ mod tests {
let buffer = cx.add_model(|cx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), cx));
let file = cx.update(|cx| tree.file("", cx));
- let history = file
- .read_with(&cx, |file, cx| {
- assert_eq!(file.path().file_name(), None);
- smol::block_on(file.save(buffer.read(cx).snapshot().text(), cx.as_ref())).unwrap();
- file.load_history(cx)
- })
- .await
- .unwrap();
-
- cx.read(|cx| assert_eq!(history.base_text.as_ref(), buffer.read(cx).text()));
+ file.read_with(&cx, |file, cx| {
+ assert_eq!(file.path().file_name(), None);
+ let text = buffer.read(cx).snapshot().text();
+ smol::block_on(file.save(text, cx.as_ref())).unwrap();
+ let new_contents = fs::read_to_string(file_path).unwrap();
+ assert_eq!(new_contents, buffer.read(cx).text());
+ });
}
#[gpui::test]