WIP: Massage opening of editors

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Nathan Sobo 2022-03-16 17:40:09 -06:00
parent 1f9885ec42
commit 728c708150
8 changed files with 338 additions and 134 deletions

View File

@ -155,7 +155,9 @@ impl ProjectDiagnosticsEditor {
async move {
for path in paths {
let buffer = project
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))
.update(&mut cx, |project, cx| {
project.open_buffer_for_path(path.clone(), cx)
})
.await?;
this.update(&mut cx, |view, cx| view.populate_excerpts(path, buffer, cx))
}
@ -449,10 +451,6 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
None
}
fn project_entry_id(&self, _: &AppContext) -> Option<project::ProjectEntryId> {
None
}
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) {
self.editor
.update(cx, |editor, cx| editor.navigate(data, cx));

View File

@ -846,10 +846,7 @@ impl Editor {
.and_then(|file| file.project_entry_id(cx))
{
return workspace
.open_item_for_project_entry(project_entry, cx, |cx| {
let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
Editor::for_buffer(multibuffer, Some(project.clone()), cx)
})
.open_editor(project_entry, cx)
.downcast::<Editor>()
.unwrap();
}
@ -8442,7 +8439,9 @@ mod tests {
.0
.read_with(cx, |tree, _| tree.id());
let buffer = project
.update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
.update(cx, |project, cx| {
project.open_buffer_for_path((worktree_id, ""), cx)
})
.await
.unwrap();
let mut fake_server = fake_servers.next().await.unwrap();

View File

@ -5,7 +5,7 @@ use gpui::{
View, ViewContext, ViewHandle, WeakModelHandle,
};
use language::{Bias, Buffer, Diagnostic, File as _};
use project::{File, Project, ProjectEntryId, ProjectPath};
use project::{File, Project, ProjectPath};
use std::fmt::Write;
use std::path::PathBuf;
use text::{Point, Selection};
@ -34,7 +34,7 @@ impl PathOpener for BufferOpener {
window_id: usize,
cx: &mut ModelContext<Project>,
) -> Option<Task<Result<Box<dyn ItemViewHandle>>>> {
let buffer = project.open_buffer(project_path, cx);
let buffer = project.open_buffer_for_path(project_path, cx);
Some(cx.spawn(|project, mut cx| async move {
let buffer = buffer.await?;
let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
@ -75,10 +75,6 @@ impl ItemView for Editor {
})
}
fn project_entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
File::from_dyn(self.buffer().read(cx).file(cx)).and_then(|file| file.project_entry_id(cx))
}
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
where
Self: Sized,

View File

@ -818,7 +818,19 @@ impl Project {
Ok(buffer)
}
pub fn open_buffer(
pub fn open_buffer_for_entry(
&mut self,
entry_id: ProjectEntryId,
cx: &mut ModelContext<Self>,
) -> Task<Result<ModelHandle<Buffer>>> {
if let Some(project_path) = self.path_for_entry(entry_id, cx) {
self.open_buffer_for_path(project_path, cx)
} else {
Task::ready(Err(anyhow!("entry not found")))
}
}
pub fn open_buffer_for_path(
&mut self,
path: impl Into<ProjectPath>,
cx: &mut ModelContext<Self>,
@ -953,8 +965,10 @@ impl Project {
worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
path: relative_path.into(),
};
this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
.await
this.update(&mut cx, |this, cx| {
this.open_buffer_for_path(project_path, cx)
})
.await
})
}
@ -2854,7 +2868,9 @@ impl Project {
let buffers_tx = buffers_tx.clone();
cx.spawn(|mut cx| async move {
if let Some(buffer) = this
.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
.update(&mut cx, |this, cx| {
this.open_buffer_for_path(project_path, cx)
})
.await
.log_err()
{
@ -3258,6 +3274,14 @@ impl Project {
.map(|entry| entry.id)
}
pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option<ProjectPath> {
let worktree = self.worktree_for_entry(entry_id, cx)?;
let worktree = worktree.read(cx);
let worktree_id = worktree.id();
let path = worktree.entry_for_id(entry_id)?.path.clone();
Some(ProjectPath { worktree_id, path })
}
// RPC message handlers
async fn handle_unshare_project(
@ -3867,7 +3891,7 @@ impl Project {
let peer_id = envelope.original_sender_id()?;
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let open_buffer = this.update(&mut cx, |this, cx| {
this.open_buffer(
this.open_buffer_for_path(
ProjectPath {
worktree_id,
path: PathBuf::from(envelope.payload.path).into(),
@ -4664,7 +4688,7 @@ mod tests {
// Open a buffer without an associated language server.
let toml_buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "Cargo.toml"), cx)
project.open_buffer_for_path((worktree_id, "Cargo.toml"), cx)
})
.await
.unwrap();
@ -4672,7 +4696,7 @@ mod tests {
// Open a buffer with an associated language server.
let rust_buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "test.rs"), cx)
project.open_buffer_for_path((worktree_id, "test.rs"), cx)
})
.await
.unwrap();
@ -4719,7 +4743,7 @@ mod tests {
// Open a third buffer with a different associated language server.
let json_buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "package.json"), cx)
project.open_buffer_for_path((worktree_id, "package.json"), cx)
})
.await
.unwrap();
@ -4750,7 +4774,7 @@ mod tests {
// it is also configured based on the existing language server's capabilities.
let rust_buffer2 = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "test2.rs"), cx)
project.open_buffer_for_path((worktree_id, "test2.rs"), cx)
})
.await
.unwrap();
@ -4861,7 +4885,7 @@ mod tests {
// Cause worktree to start the fake language server
let _buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, Path::new("b.rs")), cx)
project.open_buffer_for_path((worktree_id, Path::new("b.rs")), cx)
})
.await
.unwrap();
@ -4908,7 +4932,9 @@ mod tests {
);
let buffer = project
.update(cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
.update(cx, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.rs"), cx)
})
.await
.unwrap();
@ -4975,7 +5001,7 @@ mod tests {
let buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "a.rs"), cx)
project.open_buffer_for_path((worktree_id, "a.rs"), cx)
})
.await
.unwrap();
@ -5251,7 +5277,7 @@ mod tests {
let buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "a.rs"), cx)
project.open_buffer_for_path((worktree_id, "a.rs"), cx)
})
.await
.unwrap();
@ -5356,7 +5382,7 @@ mod tests {
let buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "a.rs"), cx)
project.open_buffer_for_path((worktree_id, "a.rs"), cx)
})
.await
.unwrap();
@ -5514,7 +5540,7 @@ mod tests {
let buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "a.rs"), cx)
project.open_buffer_for_path((worktree_id, "a.rs"), cx)
})
.await
.unwrap();
@ -5697,7 +5723,7 @@ mod tests {
let buffer = project
.update(cx, |project, cx| {
project.open_buffer(
project.open_buffer_for_path(
ProjectPath {
worktree_id,
path: Path::new("").into(),
@ -5793,7 +5819,9 @@ mod tests {
.read_with(cx, |tree, _| tree.id());
let buffer = project
.update(cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
.update(cx, |p, cx| {
p.open_buffer_for_path((worktree_id, "file1"), cx)
})
.await
.unwrap();
buffer
@ -5831,7 +5859,7 @@ mod tests {
.read_with(cx, |tree, _| tree.id());
let buffer = project
.update(cx, |p, cx| p.open_buffer((worktree_id, ""), cx))
.update(cx, |p, cx| p.open_buffer_for_path((worktree_id, ""), cx))
.await
.unwrap();
buffer
@ -5881,7 +5909,7 @@ mod tests {
let opened_buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "file1"), cx)
project.open_buffer_for_path((worktree_id, "file1"), cx)
})
.await
.unwrap();
@ -5916,7 +5944,8 @@ mod tests {
let worktree_id = tree.read_with(cx, |tree, _| tree.id());
let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
let buffer = project.update(cx, |p, cx| p.open_buffer((worktree_id, path), cx));
let buffer =
project.update(cx, |p, cx| p.open_buffer_for_path((worktree_id, path), cx));
async move { buffer.await.unwrap() }
};
let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
@ -6065,9 +6094,9 @@ mod tests {
// Spawn multiple tasks to open paths, repeating some paths.
let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| {
(
p.open_buffer((worktree_id, "a.txt"), cx),
p.open_buffer((worktree_id, "b.txt"), cx),
p.open_buffer((worktree_id, "a.txt"), cx),
p.open_buffer_for_path((worktree_id, "a.txt"), cx),
p.open_buffer_for_path((worktree_id, "b.txt"), cx),
p.open_buffer_for_path((worktree_id, "a.txt"), cx),
)
});
@ -6084,7 +6113,9 @@ mod tests {
// Open the same path again while it is still open.
drop(buffer_a_1);
let buffer_a_3 = project
.update(cx, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.update(cx, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.txt"), cx)
})
.await
.unwrap();
@ -6117,7 +6148,9 @@ mod tests {
.await;
let buffer1 = project
.update(cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
.update(cx, |p, cx| {
p.open_buffer_for_path((worktree_id, "file1"), cx)
})
.await
.unwrap();
let events = Rc::new(RefCell::new(Vec::new()));
@ -6187,7 +6220,9 @@ mod tests {
// When a file is deleted, the buffer is considered dirty.
let events = Rc::new(RefCell::new(Vec::new()));
let buffer2 = project
.update(cx, |p, cx| p.open_buffer((worktree_id, "file2"), cx))
.update(cx, |p, cx| {
p.open_buffer_for_path((worktree_id, "file2"), cx)
})
.await
.unwrap();
buffer2.update(cx, |_, cx| {
@ -6208,7 +6243,9 @@ mod tests {
// When a file is already dirty when deleted, we don't emit a Dirtied event.
let events = Rc::new(RefCell::new(Vec::new()));
let buffer3 = project
.update(cx, |p, cx| p.open_buffer((worktree_id, "file3"), cx))
.update(cx, |p, cx| {
p.open_buffer_for_path((worktree_id, "file3"), cx)
})
.await
.unwrap();
buffer3.update(cx, |_, cx| {
@ -6254,7 +6291,9 @@ mod tests {
let abs_path = dir.path().join("the-file");
let buffer = project
.update(cx, |p, cx| p.open_buffer((worktree_id, "the-file"), cx))
.update(cx, |p, cx| {
p.open_buffer_for_path((worktree_id, "the-file"), cx)
})
.await
.unwrap();
@ -6360,7 +6399,9 @@ mod tests {
let worktree_id = worktree.read_with(cx, |tree, _| tree.id());
let buffer = project
.update(cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
.update(cx, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.rs"), cx)
})
.await
.unwrap();
@ -6633,7 +6674,7 @@ mod tests {
let buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, Path::new("one.rs")), cx)
project.open_buffer_for_path((worktree_id, Path::new("one.rs")), cx)
})
.await
.unwrap();
@ -6771,7 +6812,7 @@ mod tests {
let buffer_4 = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, "four.rs"), cx)
project.open_buffer_for_path((worktree_id, "four.rs"), cx)
})
.await
.unwrap();

View File

@ -250,10 +250,6 @@ impl ItemView for ProjectSearchView {
None
}
fn project_entry_id(&self, _: &AppContext) -> Option<project::ProjectEntryId> {
None
}
fn can_save(&self, _: &gpui::AppContext) -> bool {
true
}

View File

@ -1137,7 +1137,9 @@ mod tests {
// Open the same file as client B and client A.
let buffer_b = project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "b.txt"), cx)
})
.await
.unwrap();
let buffer_b = cx_b.add_model(|cx| MultiBuffer::singleton(buffer_b, cx));
@ -1148,7 +1150,9 @@ mod tests {
assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
});
let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
.update(cx_a, |p, cx| {
p.open_buffer_for_path((worktree_id, "b.txt"), cx)
})
.await
.unwrap();
@ -1238,7 +1242,9 @@ mod tests {
.await
.unwrap();
project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.txt"), cx)
})
.await
.unwrap();
@ -1273,7 +1279,9 @@ mod tests {
.await
.unwrap();
project_b2
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.txt"), cx)
})
.await
.unwrap();
}
@ -1352,11 +1360,15 @@ mod tests {
// Open and edit a buffer as both guests B and C.
let buffer_b = project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "file1"), cx)
})
.await
.unwrap();
let buffer_c = project_c
.update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
.update(cx_c, |p, cx| {
p.open_buffer_for_path((worktree_id, "file1"), cx)
})
.await
.unwrap();
buffer_b.update(cx_b, |buf, cx| buf.edit([0..0], "i-am-b, ", cx));
@ -1364,7 +1376,9 @@ mod tests {
// Open and edit that buffer as the host.
let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
.update(cx_a, |p, cx| {
p.open_buffer_for_path((worktree_id, "file1"), cx)
})
.await
.unwrap();
@ -1514,7 +1528,9 @@ mod tests {
// Open a buffer as client B
let buffer_b = project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.txt"), cx)
})
.await
.unwrap();
@ -1597,7 +1613,9 @@ mod tests {
// Open a buffer as client B
let buffer_b = project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.txt"), cx)
})
.await
.unwrap();
buffer_b.read_with(cx_b, |buf, _| {
@ -1677,14 +1695,16 @@ mod tests {
// Open a buffer as client A
let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.update(cx_a, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.txt"), cx)
})
.await
.unwrap();
// Start opening the same buffer as client B
let buffer_b = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
let buffer_b = cx_b.background().spawn(project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.txt"), cx)
}));
// Edit the buffer as client A while client B is still opening it.
cx_b.background().simulate_random_delay().await;
@ -1760,9 +1780,9 @@ mod tests {
.await;
// Begin opening a buffer as client B, but leave the project before the open completes.
let buffer_b = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
let buffer_b = cx_b.background().spawn(project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.txt"), cx)
}));
cx_b.update(|_| drop(project_b));
drop(buffer_b);
@ -1932,7 +1952,7 @@ mod tests {
let _ = cx_a
.background()
.spawn(project_a.update(cx_a, |project, cx| {
project.open_buffer(
project.open_buffer_for_path(
ProjectPath {
worktree_id,
path: Path::new("other.rs").into(),
@ -2053,7 +2073,9 @@ mod tests {
// Open the file with the errors on client B. They should be present.
let buffer_b = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
.spawn(project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.rs"), cx)
}))
.await
.unwrap();
@ -2171,7 +2193,9 @@ mod tests {
// Open a file in an editor as the guest.
let buffer_b = project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "main.rs"), cx)
})
.await
.unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
@ -2245,7 +2269,9 @@ mod tests {
// Open the buffer on the host.
let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.update(cx_a, |p, cx| {
p.open_buffer_for_path((worktree_id, "main.rs"), cx)
})
.await
.unwrap();
buffer_a
@ -2369,7 +2395,9 @@ mod tests {
let buffer_b = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
.spawn(project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.rs"), cx)
}))
.await
.unwrap();
@ -2477,7 +2505,9 @@ mod tests {
// Open the file on client B.
let buffer_b = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
.spawn(project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.rs"), cx)
}))
.await
.unwrap();
@ -2616,7 +2646,9 @@ mod tests {
// Open the file on client B.
let buffer_b = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
.spawn(project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "one.rs"), cx)
}))
.await
.unwrap();
@ -2845,7 +2877,9 @@ mod tests {
// Open the file on client B.
let buffer_b = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
.spawn(project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "main.rs"), cx)
}))
.await
.unwrap();
@ -2992,7 +3026,9 @@ mod tests {
// Cause the language server to start.
let _buffer = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
.spawn(project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "one.rs"), cx)
}))
.await
.unwrap();
@ -3123,7 +3159,9 @@ mod tests {
let buffer_b1 = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
.spawn(project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "a.rs"), cx)
}))
.await
.unwrap();
@ -3139,9 +3177,13 @@ mod tests {
let buffer_b2;
if rng.gen() {
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
buffer_b2 = project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "b.rs"), cx)
});
} else {
buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
buffer_b2 = project_b.update(cx_b, |p, cx| {
p.open_buffer_for_path((worktree_id, "b.rs"), cx)
});
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
}
@ -4762,7 +4804,7 @@ mod tests {
);
let buffer = project
.update(&mut cx, |project, cx| {
project.open_buffer(project_path, cx)
project.open_buffer_for_path(project_path, cx)
})
.await
.unwrap();
@ -4879,7 +4921,7 @@ mod tests {
);
let buffer = project
.update(&mut cx, |project, cx| {
project.open_buffer(project_path.clone(), cx)
project.open_buffer_for_path(project_path.clone(), cx)
})
.await
.unwrap();

View File

@ -258,14 +258,13 @@ impl Pane {
if let Some(item) = item.log_err() {
pane.update(&mut cx, |pane, cx| {
pane.nav_history.borrow_mut().set_mode(mode);
let item = pane.open_item(item, cx);
pane.open_item(item, cx);
pane.nav_history
.borrow_mut()
.set_mode(NavigationMode::Normal);
if let Some(data) = entry.data {
item.navigate(data, cx);
}
item
});
} else {
workspace
@ -281,34 +280,43 @@ impl Pane {
}
}
pub fn open_item(
pub(crate) fn open_editor(
&mut self,
item_view_to_open: Box<dyn ItemViewHandle>,
project_entry_id: ProjectEntryId,
cx: &mut ViewContext<Self>,
build_editor: impl FnOnce(&mut MutableAppContext) -> Box<dyn ItemViewHandle>,
) -> Box<dyn ItemViewHandle> {
// Find an existing view for the same project entry.
for (ix, (entry_id, item_view)) in self.item_views.iter().enumerate() {
if *entry_id == item_view_to_open.project_entry_id(cx) {
for (ix, (existing_entry_id, item_view)) in self.item_views.iter().enumerate() {
if *existing_entry_id == Some(project_entry_id) {
let item_view = item_view.boxed_clone();
self.activate_item(ix, cx);
return item_view;
}
}
item_view_to_open.set_nav_history(self.nav_history.clone(), cx);
self.add_item_view(item_view_to_open.boxed_clone(), cx);
item_view_to_open
let item_view = build_editor(cx);
self.add_item(Some(project_entry_id), item_view.boxed_clone(), cx);
item_view
}
pub fn add_item_view(
pub fn open_item(
&mut self,
mut item_view: Box<dyn ItemViewHandle>,
item_view_to_open: Box<dyn ItemViewHandle>,
cx: &mut ViewContext<Self>,
) {
item_view.added_to_pane(cx);
self.add_item(None, item_view_to_open.boxed_clone(), cx);
}
pub(crate) fn add_item(
&mut self,
project_entry_id: Option<ProjectEntryId>,
mut item: Box<dyn ItemViewHandle>,
cx: &mut ViewContext<Self>,
) {
item.set_nav_history(self.nav_history.clone(), cx);
item.added_to_pane(cx);
let item_idx = cmp::min(self.active_item_index + 1, self.item_views.len());
self.item_views
.insert(item_idx, (item_view.project_entry_id(cx), item_view));
self.item_views.insert(item_idx, (project_entry_id, item));
self.activate_item(item_idx, cx);
cx.notify();
}
@ -323,6 +331,16 @@ impl Pane {
.map(|(_, view)| view.clone())
}
pub fn project_entry_id_for_item(&self, item: &dyn ItemViewHandle) -> Option<ProjectEntryId> {
self.item_views.iter().find_map(|(entry_id, existing)| {
if existing.id() == item.id() {
*entry_id
} else {
None
}
})
}
pub fn item_for_entry(&self, entry_id: ProjectEntryId) -> Option<Box<dyn ItemViewHandle>> {
self.item_views.iter().find_map(|(id, view)| {
if *id == Some(entry_id) {

View File

@ -9,6 +9,7 @@ mod status_bar;
use anyhow::{anyhow, Result};
use client::{Authenticate, ChannelList, Client, User, UserStore};
use clock::ReplicaId;
use futures::TryFutureExt;
use gpui::{
action,
color::Color,
@ -21,7 +22,7 @@ use gpui::{
MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext,
ViewHandle, WeakViewHandle,
};
use language::LanguageRegistry;
use language::{Buffer, LanguageRegistry};
use log::error;
pub use pane::*;
pub use pane_group::*;
@ -41,6 +42,15 @@ use std::{
};
use theme::{Theme, ThemeRegistry};
pub type BuildEditor = Box<
dyn Fn(
usize,
ModelHandle<Project>,
ModelHandle<Buffer>,
&mut MutableAppContext,
) -> Box<dyn ItemViewHandle>,
>;
action!(Open, Arc<AppState>);
action!(OpenNew, Arc<AppState>);
action!(OpenPaths, OpenParams);
@ -95,6 +105,16 @@ pub fn init(cx: &mut MutableAppContext) {
]);
}
pub fn register_editor_builder<F, V>(cx: &mut MutableAppContext, build_editor: F)
where
V: ItemView,
F: 'static + Fn(ModelHandle<Project>, ModelHandle<Buffer>, &mut ViewContext<V>) -> V,
{
cx.add_app_state::<BuildEditor>(Box::new(|window_id, project, model, cx| {
Box::new(cx.add_view(window_id, |cx| build_editor(project, model, cx)))
}));
}
pub struct AppState {
pub languages: Arc<LanguageRegistry>,
pub themes: Arc<ThemeRegistry>,
@ -138,7 +158,6 @@ pub trait ItemView: View {
fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) {}
fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
fn project_entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
where
@ -191,7 +210,6 @@ pub trait ItemView: View {
pub trait ItemViewHandle: 'static {
fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
fn project_entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
fn boxed_clone(&self) -> Box<dyn ItemViewHandle>;
fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, cx: &mut MutableAppContext);
fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>;
@ -239,10 +257,6 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
self.read(cx).project_path(cx)
}
fn project_entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
self.read(cx).project_entry_id(cx)
}
fn boxed_clone(&self) -> Box<dyn ItemViewHandle> {
Box::new(self.clone())
}
@ -656,6 +670,24 @@ impl Workspace {
path: ProjectPath,
cx: &mut ViewContext<Self>,
) -> Task<Result<Box<dyn ItemViewHandle>, Arc<anyhow::Error>>> {
let project_entry = self.project.read(cx).entry_for_path(&path, cx);
let existing_entry = self
.active_pane()
.update(cx, |pane, cx| pane.activate_project_entry(project_entry));
cx.spawn(|this, cx| {
if let Some(existing_entry) = existing_entry {
return Ok(existing_entry);
}
let load_task = this
.update(&mut cx, |this, cx| {
this.load_project_entry(project_entry, cx)
})
.await;
});
let load_task = self.load_path(path, cx);
let pane = self.active_pane().clone().downgrade();
cx.as_mut().spawn(|mut cx| async move {
@ -663,7 +695,7 @@ impl Workspace {
let pane = pane
.upgrade(&cx)
.ok_or_else(|| anyhow!("could not upgrade pane reference"))?;
Ok(pane.update(&mut cx, |pane, cx| pane.open_item(item, cx)))
Ok(pane.update(&mut cx, |pane, cx| pane.open_editor(item, cx)))
})
}
@ -672,13 +704,23 @@ impl Workspace {
path: ProjectPath,
cx: &mut ViewContext<Self>,
) -> Task<Result<Box<dyn ItemViewHandle>>> {
let project_entry = self.project.read(cx).entry_for_path(&path, cx);
if let Some(project_entry) = self.project.read(cx).entry_for_path(&path, cx) {
self.load_project_entry(project_entry, cx)
} else {
Task::ready(Err(anyhow!("no such file {:?}", path)))
}
}
if let Some(existing_item) = project_entry.and_then(|entry| {
self.panes
.iter()
.find_map(|pane| pane.read(cx).item_for_entry(entry))
}) {
pub fn load_project_entry(
&mut self,
project_entry: ProjectEntryId,
cx: &mut ViewContext<Self>,
) -> Task<Result<Box<dyn ItemViewHandle>>> {
if let Some(existing_item) = self
.panes
.iter()
.find_map(|pane| pane.read(cx).item_for_entry(project_entry))
{
return Task::ready(Ok(existing_item));
}
@ -829,37 +871,108 @@ impl Workspace {
pane
}
pub fn open_item(
&mut self,
item_view: Box<dyn ItemViewHandle>,
cx: &mut ViewContext<Self>,
) -> Box<dyn ItemViewHandle> {
pub fn open_item(&mut self, item_view: Box<dyn ItemViewHandle>, cx: &mut ViewContext<Self>) {
self.active_pane()
.update(cx, |pane, cx| pane.open_item(item_view, cx))
}
pub fn open_item_for_project_entry<T, F>(
pub fn open_editor(
&mut self,
project_entry: ProjectEntryId,
cx: &mut ViewContext<Self>,
build_view: F,
) -> Box<dyn ItemViewHandle>
where
T: ItemView,
F: FnOnce(&mut ViewContext<T>) -> T,
{
if let Some(existing_item) = self
.panes
.iter()
.find_map(|pane| pane.read(cx).item_for_entry(project_entry))
{
return existing_item.boxed_clone();
}
) -> Task<Result<Box<dyn ItemViewHandle>, Arc<anyhow::Error>>> {
let pane = self.active_pane().clone();
let project = self.project().clone();
let buffer = project.update(cx, |project, cx| {
project.open_buffer_for_entry(project_entry, cx)
});
let view = Box::new(cx.add_view(build_view));
self.open_item(view, cx)
cx.spawn(|this, cx| async move {
let buffer = buffer.await?;
let editor = this.update(&mut cx, |this, cx| {
let window_id = cx.window_id();
pane.update(cx, |pane, cx| {
pane.open_editor(project_entry, cx, |cx| {
cx.app_state::<BuildEditor>()(window_id, project, buffer, cx)
})
})
});
Ok(editor)
})
}
// pub fn open_path(
// &mut self,
// path: ProjectPath,
// cx: &mut ViewContext<Self>,
// ) -> Task<Result<Box<dyn ItemViewHandle>, Arc<anyhow::Error>>> {
// let project_entry = self.project.read(cx).entry_for_path(&path, cx);
// let existing_entry = self
// .active_pane()
// .update(cx, |pane, cx| pane.activate_project_entry(project_entry));
// cx.spawn(|this, cx| {
// if let Some(existing_entry) = existing_entry {
// return Ok(existing_entry);
// }
// let load_task = this
// .update(&mut cx, |this, cx| {
// this.load_project_entry(project_entry, cx)
// })
// .await;
// });
// let load_task = self.load_path(path, cx);
// let pane = self.active_pane().clone().downgrade();
// cx.as_mut().spawn(|mut cx| async move {
// let item = load_task.await?;
// let pane = pane
// .upgrade(&cx)
// .ok_or_else(|| anyhow!("could not upgrade pane reference"))?;
// Ok(pane.update(&mut cx, |pane, cx| pane.open_editor(item, cx)))
// })
// }
// pub fn load_path(
// &mut self,
// path: ProjectPath,
// cx: &mut ViewContext<Self>,
// ) -> Task<Result<Box<dyn ItemViewHandle>>> {
// if let Some(project_entry) = self.project.read(cx).entry_for_path(&path, cx) {
// self.load_project_entry(project_entry, cx)
// } else {
// Task::ready(Err(anyhow!("no such file {:?}", path)))
// }
// }
// pub fn load_project_entry(
// &mut self,
// project_entry: ProjectEntryId,
// cx: &mut ViewContext<Self>,
// ) -> Task<Result<Box<dyn ItemViewHandle>>> {
// if let Some(existing_item) = self
// .panes
// .iter()
// .find_map(|pane| pane.read(cx).item_for_entry(project_entry))
// {
// return Task::ready(Ok(existing_item));
// }
// let project_path = path.clone();
// let path_openers = self.path_openers.clone();
// let window_id = cx.window_id();
// self.project.update(cx, |project, cx| {
// for opener in path_openers.iter() {
// if let Some(task) = opener.open(project, project_path.clone(), window_id, cx) {
// return task;
// }
// }
// Task::ready(Err(anyhow!("no opener found for path {:?}", project_path)))
// })
// }
pub fn activate_item(&mut self, item: &dyn ItemViewHandle, cx: &mut ViewContext<Self>) -> bool {
let result = self.panes.iter().find_map(|pane| {
if let Some(ix) = pane.read(cx).index_for_item(item) {
@ -930,10 +1043,11 @@ impl Workspace {
let new_pane = self.add_pane(cx);
self.activate_pane(new_pane.clone(), cx);
if let Some(item) = pane.read(cx).active_item() {
let nav_history = new_pane.read(cx).nav_history().clone();
let project_entry_id = pane.read(cx).project_entry_id_for_item(item.as_ref());
if let Some(clone) = item.clone_on_split(cx.as_mut()) {
clone.set_nav_history(nav_history, cx);
new_pane.update(cx, |new_pane, cx| new_pane.add_item_view(clone, cx));
new_pane.update(cx, |new_pane, cx| {
new_pane.open_item(project_entry_id, clone, cx);
});
}
}
self.center.split(&pane, &new_pane, direction).unwrap();