Reuse views when moving between diagnostic view and editors

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2022-01-07 11:00:12 -08:00
parent ea263822fa
commit ce6f3d7f3e
6 changed files with 103 additions and 21 deletions

View file

@ -165,9 +165,14 @@ impl ProjectDiagnosticsEditor {
} }
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) { fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone())); if let Some(existing) = workspace.item_of_type::<ProjectDiagnostics>(cx) {
workspace.activate_pane_for_item(&existing, cx);
} else {
let diagnostics =
cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone()));
workspace.open_item(diagnostics, cx); workspace.open_item(diagnostics, cx);
} }
}
fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) { fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
if let Some(workspace) = self.workspace.upgrade(cx) { if let Some(workspace) = self.workspace.upgrade(cx) {
@ -191,8 +196,10 @@ impl ProjectDiagnosticsEditor {
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
for (buffer, ranges) in new_selections_by_buffer { for (buffer, ranges) in new_selections_by_buffer {
let buffer = BufferItemHandle(buffer);
workspace.activate_pane_for_item(&buffer, cx);
let editor = workspace let editor = workspace
.open_item(BufferItemHandle(buffer), cx) .open_item(buffer, cx)
.to_any() .to_any()
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap();

View file

@ -62,6 +62,10 @@ impl ItemHandle for BufferItemHandle {
Box::new(self.clone()) Box::new(self.clone())
} }
fn to_any(&self) -> gpui::AnyModelHandle {
self.0.clone().into()
}
fn downgrade(&self) -> Box<dyn workspace::WeakItemHandle> { fn downgrade(&self) -> Box<dyn workspace::WeakItemHandle> {
Box::new(WeakBufferItemHandle(self.0.downgrade())) Box::new(WeakBufferItemHandle(self.0.downgrade()))
} }

View file

@ -3097,14 +3097,39 @@ impl Drop for AnyViewHandle {
pub struct AnyModelHandle { pub struct AnyModelHandle {
model_id: usize, model_id: usize,
model_type: TypeId,
ref_counts: Arc<Mutex<RefCounts>>, ref_counts: Arc<Mutex<RefCounts>>,
} }
impl AnyModelHandle {
pub fn downcast<T: Entity>(self) -> Option<ModelHandle<T>> {
if self.is::<T>() {
let result = Some(ModelHandle {
model_id: self.model_id,
model_type: PhantomData,
ref_counts: self.ref_counts.clone(),
});
unsafe {
Arc::decrement_strong_count(&self.ref_counts);
}
std::mem::forget(self);
result
} else {
None
}
}
pub fn is<T: Entity>(&self) -> bool {
self.model_type == TypeId::of::<T>()
}
}
impl<T: Entity> From<ModelHandle<T>> for AnyModelHandle { impl<T: Entity> From<ModelHandle<T>> for AnyModelHandle {
fn from(handle: ModelHandle<T>) -> Self { fn from(handle: ModelHandle<T>) -> Self {
handle.ref_counts.lock().inc_model(handle.model_id); handle.ref_counts.lock().inc_model(handle.model_id);
Self { Self {
model_id: handle.model_id, model_id: handle.model_id,
model_type: TypeId::of::<T>(),
ref_counts: handle.ref_counts.clone(), ref_counts: handle.ref_counts.clone(),
} }
} }

View file

@ -69,7 +69,7 @@ pub struct TabState {
} }
pub struct Pane { pub struct Pane {
item_views: Vec<Box<dyn ItemViewHandle>>, item_views: Vec<(usize, Box<dyn ItemViewHandle>)>,
active_item: usize, active_item: usize,
settings: watch::Receiver<Settings>, settings: watch::Receiver<Settings>,
} }
@ -96,8 +96,8 @@ impl Pane {
where where
T: 'static + ItemHandle, T: 'static + ItemHandle,
{ {
for (ix, item_view) in self.item_views.iter().enumerate() { for (ix, (item_id, item_view)) in self.item_views.iter().enumerate() {
if item_view.item_handle(cx).id() == item_handle.id() { if *item_id == item_handle.id() {
let item_view = item_view.boxed_clone(); let item_view = item_view.boxed_clone();
self.activate_item(ix, cx); self.activate_item(ix, cx);
return item_view; return item_view;
@ -116,21 +116,33 @@ impl Pane {
) { ) {
item_view.added_to_pane(cx); item_view.added_to_pane(cx);
let item_idx = cmp::min(self.active_item + 1, self.item_views.len()); let item_idx = cmp::min(self.active_item + 1, self.item_views.len());
self.item_views.insert(item_idx, item_view); self.item_views
.insert(item_idx, (item_view.item_handle(cx).id(), item_view));
self.activate_item(item_idx, cx); self.activate_item(item_idx, cx);
cx.notify(); cx.notify();
} }
pub fn item_views(&self) -> &[Box<dyn ItemViewHandle>] { pub fn contains_item(&self, item: &dyn ItemHandle) -> bool {
&self.item_views let item_id = item.id();
self.item_views
.iter()
.any(|(existing_item_id, _)| *existing_item_id == item_id)
}
pub fn item_views(&self) -> impl Iterator<Item = &Box<dyn ItemViewHandle>> {
self.item_views.iter().map(|(_, view)| view)
} }
pub fn active_item(&self) -> Option<Box<dyn ItemViewHandle>> { pub fn active_item(&self) -> Option<Box<dyn ItemViewHandle>> {
self.item_views.get(self.active_item).cloned() self.item_views
.get(self.active_item)
.map(|(_, view)| view.clone())
} }
pub fn item_index(&self, item: &dyn ItemViewHandle) -> Option<usize> { pub fn item_index(&self, item: &dyn ItemViewHandle) -> Option<usize> {
self.item_views.iter().position(|i| i.id() == item.id()) self.item_views
.iter()
.position(|(_, i)| i.id() == item.id())
} }
pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext<Self>) { pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext<Self>) {
@ -163,12 +175,12 @@ impl Pane {
pub fn close_active_item(&mut self, cx: &mut ViewContext<Self>) { pub fn close_active_item(&mut self, cx: &mut ViewContext<Self>) {
if !self.item_views.is_empty() { if !self.item_views.is_empty() {
self.close_item(self.item_views[self.active_item].id(), cx) self.close_item(self.item_views[self.active_item].1.id(), cx)
} }
} }
pub fn close_item(&mut self, item_id: usize, cx: &mut ViewContext<Self>) { pub fn close_item(&mut self, item_id: usize, cx: &mut ViewContext<Self>) {
self.item_views.retain(|item| item.id() != item_id); self.item_views.retain(|(_, item)| item.id() != item_id);
self.active_item = cmp::min(self.active_item, self.item_views.len().saturating_sub(1)); self.active_item = cmp::min(self.active_item, self.item_views.len().saturating_sub(1));
if self.item_views.is_empty() { if self.item_views.is_empty() {
cx.emit(Event::Remove); cx.emit(Event::Remove);
@ -193,7 +205,7 @@ impl Pane {
enum Tabs {} enum Tabs {}
let tabs = MouseEventHandler::new::<Tabs, _, _, _>(cx.view_id(), cx, |mouse_state, cx| { let tabs = MouseEventHandler::new::<Tabs, _, _, _>(cx.view_id(), cx, |mouse_state, cx| {
let mut row = Flex::row(); let mut row = Flex::row();
for (ix, item_view) in self.item_views.iter().enumerate() { for (ix, (_, item_view)) in self.item_views.iter().enumerate() {
let is_active = ix == self.active_item; let is_active = ix == self.active_item;
row.add_child({ row.add_child({

View file

@ -16,9 +16,9 @@ use gpui::{
json::{self, to_string_pretty, ToJson}, json::{self, to_string_pretty, ToJson},
keymap::Binding, keymap::Binding,
platform::{CursorStyle, WindowOptions}, platform::{CursorStyle, WindowOptions},
AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext, AnyModelHandle, AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle,
PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext,
WeakModelHandle, WeakViewHandle, ViewHandle, WeakModelHandle, WeakViewHandle,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use log::error; use log::error;
@ -186,6 +186,7 @@ pub trait ItemHandle: Send + Sync {
) -> Box<dyn ItemViewHandle>; ) -> Box<dyn ItemViewHandle>;
fn boxed_clone(&self) -> Box<dyn ItemHandle>; fn boxed_clone(&self) -> Box<dyn ItemHandle>;
fn downgrade(&self) -> Box<dyn WeakItemHandle>; fn downgrade(&self) -> Box<dyn WeakItemHandle>;
fn to_any(&self) -> AnyModelHandle;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>; fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
} }
@ -238,6 +239,10 @@ impl<T: Item> ItemHandle for ModelHandle<T> {
Box::new(self.downgrade()) Box::new(self.downgrade())
} }
fn to_any(&self) -> AnyModelHandle {
self.clone().into()
}
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> { fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
self.read(cx).project_path() self.read(cx).project_path()
} }
@ -265,6 +270,10 @@ impl ItemHandle for Box<dyn ItemHandle> {
self.as_ref().downgrade() self.as_ref().downgrade()
} }
fn to_any(&self) -> AnyModelHandle {
self.as_ref().to_any()
}
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> { fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
self.as_ref().project_path(cx) self.as_ref().project_path(cx)
} }
@ -760,6 +769,12 @@ impl Workspace {
.find(|i| i.project_path(cx).as_ref() == Some(path)) .find(|i| i.project_path(cx).as_ref() == Some(path))
} }
pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ModelHandle<T>> {
self.items
.iter()
.find_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast()))
}
pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemViewHandle>> { pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemViewHandle>> {
self.active_pane().read(cx).active_item() self.active_pane().read(cx).active_item()
} }
@ -927,6 +942,26 @@ impl Workspace {
pane.update(cx, |pane, cx| pane.open_item(item_handle, self, cx)) pane.update(cx, |pane, cx| pane.open_item(item_handle, self, cx))
} }
pub fn activate_pane_for_item(
&mut self,
item: &dyn ItemHandle,
cx: &mut ViewContext<Self>,
) -> bool {
let pane = self.panes.iter().find_map(|pane| {
if pane.read(cx).contains_item(item) {
Some(pane.clone())
} else {
None
}
});
if let Some(pane) = pane {
self.activate_pane(pane.clone(), cx);
true
} else {
false
}
}
fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) { fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
self.active_pane = pane; self.active_pane = pane;
self.status_bar.update(cx, |status_bar, cx| { self.status_bar.update(cx, |status_bar, cx| {

View file

@ -273,7 +273,7 @@ mod tests {
pane.active_item().unwrap().project_path(cx), pane.active_item().unwrap().project_path(cx),
Some(file1.clone()) Some(file1.clone())
); );
assert_eq!(pane.item_views().len(), 1); assert_eq!(pane.item_views().count(), 1);
}); });
// Open the second entry // Open the second entry
@ -287,7 +287,7 @@ mod tests {
pane.active_item().unwrap().project_path(cx), pane.active_item().unwrap().project_path(cx),
Some(file2.clone()) Some(file2.clone())
); );
assert_eq!(pane.item_views().len(), 2); assert_eq!(pane.item_views().count(), 2);
}); });
// Open the first entry again. The existing pane item is activated. // Open the first entry again. The existing pane item is activated.
@ -303,7 +303,7 @@ mod tests {
pane.active_item().unwrap().project_path(cx), pane.active_item().unwrap().project_path(cx),
Some(file1.clone()) Some(file1.clone())
); );
assert_eq!(pane.item_views().len(), 2); assert_eq!(pane.item_views().count(), 2);
}); });
// Split the pane with the first entry, then open the second entry again. // Split the pane with the first entry, then open the second entry again.
@ -343,7 +343,6 @@ mod tests {
); );
let pane_entries = pane let pane_entries = pane
.item_views() .item_views()
.iter()
.map(|i| i.project_path(cx).unwrap()) .map(|i| i.project_path(cx).unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(pane_entries, &[file1, file2, file3]); assert_eq!(pane_entries, &[file1, file2, file3]);