Compare commits
8 Commits
fix-git-ht
...
panel-foll
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e3063b65e | ||
|
|
6b3d53159e | ||
|
|
1641f0f73d | ||
|
|
8f3cb69daa | ||
|
|
6bebf42ed7 | ||
|
|
abf9f86fb7 | ||
|
|
6591344b60 | ||
|
|
8bfab8fef6 |
@@ -18,6 +18,7 @@ use crate::{
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
||||
use breadcrumbs::Breadcrumbs;
|
||||
use client::proto;
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use editor::{
|
||||
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
|
||||
@@ -58,7 +59,7 @@ use ui::{
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
item::{BreadcrumbText, Item, ItemHandle},
|
||||
item::{self, BreadcrumbText, FollowableItem, FollowableItemHandle, Item, ItemHandle},
|
||||
pane,
|
||||
searchable::{SearchEvent, SearchableItem},
|
||||
Pane, Save, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
@@ -66,6 +67,7 @@ use workspace::{
|
||||
use workspace::{searchable::SearchableItemHandle, NewFile};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
workspace
|
||||
@@ -579,9 +581,10 @@ impl AssistantPanel {
|
||||
});
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = ContextEditor::for_context(
|
||||
let mut editor = ContextEditor::new(
|
||||
context,
|
||||
self.fs.clone(),
|
||||
self.project.clone(),
|
||||
workspace,
|
||||
lsp_adapter_delegate,
|
||||
cx,
|
||||
@@ -613,12 +616,13 @@ impl AssistantPanel {
|
||||
fn handle_context_editor_event(
|
||||
&mut self,
|
||||
_: View<ContextEditor>,
|
||||
event: &ContextEditorEvent,
|
||||
event: &EditorEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ContextEditorEvent::TabContentChanged => cx.notify(),
|
||||
ContextEditorEvent::Edited => cx.emit(AssistantPanelEvent::ContextEdited),
|
||||
EditorEvent::TitleChanged { .. } => cx.notify(),
|
||||
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,6 +631,7 @@ impl AssistantPanel {
|
||||
.pane
|
||||
.read(cx)
|
||||
.items()
|
||||
.iter()
|
||||
.position(|item| item.downcast::<ContextHistory>().is_some());
|
||||
|
||||
if let Some(history_item_ix) = history_item_ix {
|
||||
@@ -679,7 +684,7 @@ impl AssistantPanel {
|
||||
path: PathBuf,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let existing_context = self.pane.read(cx).items().find_map(|item| {
|
||||
let existing_context = self.pane.read(cx).items().iter().find_map(|item| {
|
||||
item.downcast::<ContextEditor>()
|
||||
.filter(|editor| editor.read(cx).context.read(cx).path() == Some(&path))
|
||||
});
|
||||
@@ -694,6 +699,7 @@ impl AssistantPanel {
|
||||
.update(cx, |store, cx| store.open_local_context(path.clone(), cx));
|
||||
let fs = self.fs.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
let project = self.project.clone();
|
||||
|
||||
let lsp_adapter_delegate = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
@@ -709,7 +715,7 @@ impl AssistantPanel {
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("workspace dropped"))?;
|
||||
let editor = cx.new_view(|cx| {
|
||||
ContextEditor::for_context(context, fs, workspace, lsp_adapter_delegate, cx)
|
||||
ContextEditor::new(context, fs, project, workspace, lsp_adapter_delegate, cx)
|
||||
});
|
||||
this.show_context(editor, cx);
|
||||
anyhow::Ok(())
|
||||
@@ -722,14 +728,17 @@ impl AssistantPanel {
|
||||
&mut self,
|
||||
id: ContextId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let existing_context = self.pane.read(cx).items().find_map(|item| {
|
||||
) -> Task<Result<View<ContextEditor>>> {
|
||||
let existing_context = self.pane.read(cx).items().iter().find_map(|item| {
|
||||
item.downcast::<ContextEditor>()
|
||||
.filter(|editor| *editor.read(cx).context.read(cx).id() == id)
|
||||
});
|
||||
if let Some(existing_context) = existing_context {
|
||||
return cx.spawn(|this, mut cx| async move {
|
||||
this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.show_context(existing_context.clone(), cx)
|
||||
})?;
|
||||
Ok(existing_context)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -737,6 +746,7 @@ impl AssistantPanel {
|
||||
.context_store
|
||||
.update(cx, |store, cx| store.open_remote_context(id, cx));
|
||||
let fs = self.fs.clone();
|
||||
let project = self.project.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
let lsp_adapter_delegate = workspace
|
||||
@@ -753,12 +763,11 @@ impl AssistantPanel {
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("workspace dropped"))?;
|
||||
let editor = cx.new_view(|cx| {
|
||||
ContextEditor::for_context(context, fs, workspace, lsp_adapter_delegate, cx)
|
||||
ContextEditor::new(context, fs, project, workspace, lsp_adapter_delegate, cx)
|
||||
});
|
||||
this.show_context(editor, cx);
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
Ok(())
|
||||
this.show_context(editor.clone(), cx);
|
||||
anyhow::Ok(editor)
|
||||
})?
|
||||
})
|
||||
}
|
||||
|
||||
@@ -878,6 +887,37 @@ impl Panel for AssistantPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
|
||||
self.pane.read(cx).active_item()
|
||||
}
|
||||
|
||||
fn items<'a>(&'a self, cx: &'a AppContext) -> &'a [Box<dyn ItemHandle>] {
|
||||
self.pane.read(cx).items()
|
||||
}
|
||||
|
||||
fn remote_id() -> Option<proto::PanelId> {
|
||||
Some(proto::PanelId::AssistantPanel)
|
||||
}
|
||||
|
||||
fn leader_updated(&mut self, view: Box<dyn FollowableItemHandle>, cx: &mut ViewContext<Self>) {
|
||||
let item = ItemHandle::boxed_clone(view.as_ref());
|
||||
self.pane.update(cx, |pane, cx| {
|
||||
if let Some(index) = pane.index_for_item(item.as_ref()) {
|
||||
pane.activate_item(index, false, false, cx);
|
||||
} else {
|
||||
pane.add_item(item, false, false, None, cx)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn dedup(
|
||||
&mut self,
|
||||
view: Box<dyn FollowableItemHandle>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Box<dyn FollowableItemHandle> {
|
||||
self.pane.update(cx, |pane, cx| pane.dedup(view, cx))
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
if !settings.enabled || !settings.button {
|
||||
@@ -924,6 +964,7 @@ pub struct ContextEditor {
|
||||
editor: View<Editor>,
|
||||
blocks: HashSet<BlockId>,
|
||||
scroll_position: Option<ScrollPosition>,
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||
pending_slash_command_blocks: HashMap<Range<language::Anchor>, BlockId>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
@@ -932,9 +973,10 @@ pub struct ContextEditor {
|
||||
impl ContextEditor {
|
||||
const MAX_TAB_TITLE_LEN: usize = 16;
|
||||
|
||||
fn for_context(
|
||||
fn new(
|
||||
context: Model<Context>,
|
||||
fs: Arc<dyn Fs>,
|
||||
project: Model<Project>,
|
||||
workspace: View<Workspace>,
|
||||
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
@@ -954,6 +996,7 @@ impl ContextEditor {
|
||||
editor.set_show_wrap_guides(false, cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_completion_provider(Box::new(completion_provider));
|
||||
editor.set_collaboration_hub(Box::new(project));
|
||||
editor
|
||||
});
|
||||
|
||||
@@ -971,6 +1014,7 @@ impl ContextEditor {
|
||||
lsp_adapter_delegate,
|
||||
blocks: Default::default(),
|
||||
scroll_position: None,
|
||||
remote_id: None,
|
||||
fs,
|
||||
workspace: workspace.downgrade(),
|
||||
pending_slash_command_creases: HashMap::default(),
|
||||
@@ -1213,7 +1257,7 @@ impl ContextEditor {
|
||||
});
|
||||
}
|
||||
ContextEvent::SummaryChanged => {
|
||||
cx.emit(ContextEditorEvent::TabContentChanged);
|
||||
cx.emit(EditorEvent::TitleChanged);
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(None, self.fs.clone(), cx);
|
||||
});
|
||||
@@ -1472,9 +1516,9 @@ impl ContextEditor {
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
self.scroll_position = self.cursor_scroll_position(cx);
|
||||
}
|
||||
EditorEvent::BufferEdited => cx.emit(ContextEditorEvent::Edited),
|
||||
_ => {}
|
||||
}
|
||||
cx.emit(event.clone());
|
||||
}
|
||||
|
||||
fn handle_editor_search_event(
|
||||
@@ -1935,7 +1979,7 @@ impl ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ContextEditorEvent> for ContextEditor {}
|
||||
impl EventEmitter<EditorEvent> for ContextEditor {}
|
||||
impl EventEmitter<SearchEvent> for ContextEditor {}
|
||||
|
||||
impl Render for ContextEditor {
|
||||
@@ -1977,13 +2021,9 @@ impl FocusableView for ContextEditor {
|
||||
}
|
||||
|
||||
impl Item for ContextEditor {
|
||||
type Event = ContextEditorEvent;
|
||||
type Event = editor::EditorEvent;
|
||||
|
||||
fn tab_content(
|
||||
&self,
|
||||
params: workspace::item::TabContentParams,
|
||||
cx: &WindowContext,
|
||||
) -> AnyElement {
|
||||
fn tab_content(&self, params: item::TabContentParams, cx: &WindowContext) -> AnyElement {
|
||||
let color = if params.selected {
|
||||
Color::Default
|
||||
} else {
|
||||
@@ -1997,15 +2037,16 @@ impl Item for ContextEditor {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
|
||||
match event {
|
||||
ContextEditorEvent::Edited => {
|
||||
f(workspace::item::ItemEvent::Edit);
|
||||
f(workspace::item::ItemEvent::UpdateBreadcrumbs);
|
||||
EditorEvent::Edited { .. } => {
|
||||
f(item::ItemEvent::Edit);
|
||||
f(item::ItemEvent::UpdateBreadcrumbs);
|
||||
}
|
||||
ContextEditorEvent::TabContentChanged => {
|
||||
f(workspace::item::ItemEvent::UpdateTab);
|
||||
EditorEvent::TitleChanged => {
|
||||
f(item::ItemEvent::UpdateTab);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2021,7 +2062,7 @@ impl Item for ContextEditor {
|
||||
&self,
|
||||
theme: &theme::Theme,
|
||||
cx: &AppContext,
|
||||
) -> Option<Vec<workspace::item::BreadcrumbText>> {
|
||||
) -> Option<Vec<item::BreadcrumbText>> {
|
||||
let editor = self.editor.read(cx);
|
||||
let cursor = editor.selections.newest_anchor().head();
|
||||
let multibuffer = &editor.buffer().read(cx);
|
||||
@@ -2133,6 +2174,131 @@ impl SearchableItem for ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
impl FollowableItem for ContextEditor {
|
||||
fn remote_id(&self) -> Option<workspace::ViewId> {
|
||||
self.remote_id
|
||||
}
|
||||
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
|
||||
let context = self.context.read(cx);
|
||||
Some(proto::view::Variant::ContextEditor(
|
||||
proto::view::ContextEditor {
|
||||
context_id: context.id().to_proto(),
|
||||
editor: if let Some(proto::view::Variant::Editor(proto)) =
|
||||
self.editor.read(cx).to_state_proto(cx)
|
||||
{
|
||||
Some(proto)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn from_state_proto(
|
||||
workspace: View<Workspace>,
|
||||
id: workspace::ViewId,
|
||||
state: &mut Option<proto::view::Variant>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Task<Result<View<Self>>>> {
|
||||
let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
|
||||
return None;
|
||||
};
|
||||
let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let context_id = ContextId::from_proto(state.context_id);
|
||||
let editor_state = state.editor?;
|
||||
|
||||
let (project, panel) = workspace.update(cx, |workspace, cx| {
|
||||
Some((
|
||||
workspace.project().clone(),
|
||||
workspace.panel::<AssistantPanel>(cx)?,
|
||||
))
|
||||
})?;
|
||||
|
||||
let context_editor =
|
||||
panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
|
||||
|
||||
Some(cx.spawn(|mut cx| async move {
|
||||
let context_editor = context_editor.await?;
|
||||
context_editor
|
||||
.update(&mut cx, |context_editor, cx| {
|
||||
context_editor.remote_id = Some(id);
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
editor.apply_update_proto(
|
||||
&project,
|
||||
proto::update_view::Variant::Editor(proto::update_view::Editor {
|
||||
selections: editor_state.selections,
|
||||
pending_selection: editor_state.pending_selection,
|
||||
scroll_top_anchor: editor_state.scroll_top_anchor,
|
||||
scroll_x: editor_state.scroll_y,
|
||||
scroll_y: editor_state.scroll_y,
|
||||
..Default::default()
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})?
|
||||
.await?;
|
||||
Ok(context_editor)
|
||||
}))
|
||||
}
|
||||
|
||||
fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
|
||||
Editor::to_follow_event(event)
|
||||
}
|
||||
|
||||
fn add_event_to_update_proto(
|
||||
&self,
|
||||
event: &Self::Event,
|
||||
update: &mut Option<proto::update_view::Variant>,
|
||||
cx: &WindowContext,
|
||||
) -> bool {
|
||||
self.editor
|
||||
.read(cx)
|
||||
.add_event_to_update_proto(event, update, cx)
|
||||
}
|
||||
|
||||
fn apply_update_proto(
|
||||
&mut self,
|
||||
project: &Model<Project>,
|
||||
message: proto::update_view::Variant,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.apply_update_proto(project, message, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn is_project_item(&self, _cx: &WindowContext) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_leader_peer_id(
|
||||
&mut self,
|
||||
leader_peer_id: Option<proto::PeerId>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_leader_peer_id(leader_peer_id, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn leader_peer_id(&self, cx: &AppContext) -> Option<proto::PeerId> {
|
||||
self.editor.read(cx).leader_peer_id(cx)
|
||||
}
|
||||
|
||||
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
|
||||
if existing.context.read(cx).id() == self.context.read(cx).id() {
|
||||
Some(item::Dedup::KeepExisting)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContextEditorToolbarItem {
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakView<Workspace>,
|
||||
@@ -2369,11 +2535,7 @@ impl EventEmitter<()> for ContextHistory {}
|
||||
impl Item for ContextHistory {
|
||||
type Event = ();
|
||||
|
||||
fn tab_content(
|
||||
&self,
|
||||
params: workspace::item::TabContentParams,
|
||||
_: &WindowContext,
|
||||
) -> AnyElement {
|
||||
fn tab_content(&self, params: item::TabContentParams, _: &WindowContext) -> AnyElement {
|
||||
let color = if params.selected {
|
||||
Color::Default
|
||||
} else {
|
||||
|
||||
@@ -135,7 +135,7 @@ async fn test_basic_following(
|
||||
assert_eq!(editor.selections.ranges(cx), vec![2..1]);
|
||||
});
|
||||
|
||||
// When client B starts following client A, all visible view states are replicated to client B.
|
||||
// When client B starts following client A, only the active view state is replicated to client B.
|
||||
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
|
||||
|
||||
cx_c.executor().run_until_parked();
|
||||
@@ -156,7 +156,7 @@ async fn test_basic_following(
|
||||
);
|
||||
assert_eq!(
|
||||
editor_b1.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||
vec![3..2]
|
||||
vec![3..3]
|
||||
);
|
||||
|
||||
executor.run_until_parked();
|
||||
@@ -601,6 +601,7 @@ async fn test_following_tab_order(
|
||||
let pane_paths = |pane: &View<workspace::Pane>, cx: &mut VisualTestContext| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.items()
|
||||
.iter()
|
||||
.map(|item| {
|
||||
item.project_path(cx)
|
||||
.unwrap()
|
||||
@@ -1829,6 +1830,7 @@ fn pane_summaries(workspace: &View<Workspace>, cx: &mut VisualTestContext) -> Ve
|
||||
leader,
|
||||
items: pane
|
||||
.items()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(ix, item)| {
|
||||
(
|
||||
|
||||
@@ -6243,10 +6243,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(1).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[1].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(!pane.can_navigate_forward());
|
||||
@@ -6264,10 +6261,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_2.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(1).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[1].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(!pane.can_navigate_forward());
|
||||
@@ -6283,10 +6277,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(1).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[1].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(pane.can_navigate_forward());
|
||||
@@ -6322,10 +6313,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_3.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(1).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[1].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(pane.can_navigate_forward());
|
||||
@@ -6333,7 +6321,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
|
||||
// Close permanent tab
|
||||
pane.update(cx, |pane, cx| {
|
||||
let id = pane.items().nth(0).unwrap().item_id();
|
||||
let id = pane.items()[0].item_id();
|
||||
pane.close_item_by_id(id, workspace::SaveIntent::Skip, cx)
|
||||
})
|
||||
.await
|
||||
@@ -6342,10 +6330,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(0).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[0].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(pane.can_navigate_forward());
|
||||
@@ -6361,10 +6346,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(0).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[0].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(pane.can_navigate_forward());
|
||||
@@ -6390,10 +6372,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(0).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[0].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(pane.can_navigate_forward());
|
||||
@@ -6403,10 +6382,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_2.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(1).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[1].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(!pane.can_navigate_forward());
|
||||
@@ -6428,10 +6404,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(get_path(pane, 0, cx), path_2.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(0).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[0].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(!pane.can_navigate_forward());
|
||||
@@ -6441,10 +6414,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(get_path(pane, 1, cx), path_2.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().nth(1).unwrap().item_id())
|
||||
);
|
||||
assert_eq!(pane.preview_item_id(), Some(pane.items()[1].item_id()));
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
assert!(!pane.can_navigate_forward());
|
||||
|
||||
@@ -22,10 +22,9 @@ use std::{
|
||||
};
|
||||
use ui::{prelude::*, Label};
|
||||
use util::ResultExt;
|
||||
use workspace::notifications::NotificationId;
|
||||
use workspace::{item::Dedup, notifications::NotificationId};
|
||||
use workspace::{
|
||||
item::{FollowableItem, Item, ItemEvent, ItemHandle, TabContentParams},
|
||||
register_followable_item,
|
||||
searchable::SearchableItemHandle,
|
||||
ItemNavHistory, Pane, SaveIntent, Toast, ViewId, Workspace, WorkspaceId,
|
||||
};
|
||||
@@ -33,7 +32,7 @@ use workspace::{
|
||||
actions!(collab, [CopyLink]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
register_followable_item::<ChannelView>(cx)
|
||||
workspace::FollowableViewRegistry::register::<ChannelView>(cx)
|
||||
}
|
||||
|
||||
pub struct ChannelView {
|
||||
@@ -83,6 +82,56 @@ impl ChannelView {
|
||||
pane: View<Pane>,
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
let channel_view = Self::load(channel_id, workspace, cx);
|
||||
cx.spawn(|mut cx| async move {
|
||||
let channel_view = channel_view.await?;
|
||||
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
let buffer_id = channel_view.read(cx).channel_buffer.read(cx).remote_id(cx);
|
||||
|
||||
let existing_view = pane
|
||||
.items_of_type::<Self>()
|
||||
.find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);
|
||||
|
||||
// If this channel buffer is already open in this pane, just return it.
|
||||
if let Some(existing_view) = existing_view.clone() {
|
||||
if existing_view.read(cx).channel_buffer == channel_view.read(cx).channel_buffer
|
||||
{
|
||||
if let Some(link_position) = link_position {
|
||||
existing_view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
});
|
||||
}
|
||||
return existing_view;
|
||||
}
|
||||
}
|
||||
|
||||
// If the pane contained a disconnected view for this channel buffer,
|
||||
// replace that.
|
||||
if let Some(existing_item) = existing_view {
|
||||
if let Some(ix) = pane.index_for_item(&existing_item) {
|
||||
pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, cx)
|
||||
.detach();
|
||||
pane.add_item(Box::new(channel_view.clone()), true, true, Some(ix), cx);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(link_position) = link_position {
|
||||
channel_view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
});
|
||||
}
|
||||
|
||||
channel_view
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
channel_id: ChannelId,
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
let weak_workspace = workspace.downgrade();
|
||||
let workspace = workspace.read(cx);
|
||||
@@ -107,49 +156,11 @@ impl ChannelView {
|
||||
})
|
||||
})?;
|
||||
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
let buffer_id = channel_buffer.read(cx).remote_id(cx);
|
||||
|
||||
let existing_view = pane
|
||||
.items_of_type::<Self>()
|
||||
.find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);
|
||||
|
||||
// If this channel buffer is already open in this pane, just return it.
|
||||
if let Some(existing_view) = existing_view.clone() {
|
||||
if existing_view.read(cx).channel_buffer == channel_buffer {
|
||||
if let Some(link_position) = link_position {
|
||||
existing_view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
});
|
||||
}
|
||||
return existing_view;
|
||||
}
|
||||
}
|
||||
|
||||
let view = cx.new_view(|cx| {
|
||||
let mut this =
|
||||
Self::new(project, weak_workspace, channel_store, channel_buffer, cx);
|
||||
this.acknowledge_buffer_version(cx);
|
||||
this
|
||||
});
|
||||
|
||||
// If the pane contained a disconnected view for this channel buffer,
|
||||
// replace that.
|
||||
if let Some(existing_item) = existing_view {
|
||||
if let Some(ix) = pane.index_for_item(&existing_item) {
|
||||
pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, cx)
|
||||
.detach();
|
||||
pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(link_position) = link_position {
|
||||
view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
});
|
||||
}
|
||||
|
||||
view
|
||||
cx.new_view(|cx| {
|
||||
let mut this =
|
||||
Self::new(project, weak_workspace, channel_store, channel_buffer, cx);
|
||||
this.acknowledge_buffer_version(cx);
|
||||
this
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -478,7 +489,6 @@ impl FollowableItem for ChannelView {
|
||||
}
|
||||
|
||||
fn from_state_proto(
|
||||
pane: View<workspace::Pane>,
|
||||
workspace: View<workspace::Workspace>,
|
||||
remote_id: workspace::ViewId,
|
||||
state: &mut Option<proto::view::Variant>,
|
||||
@@ -491,8 +501,7 @@ impl FollowableItem for ChannelView {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let open =
|
||||
ChannelView::open_in_pane(ChannelId(state.channel_id), None, pane, workspace, cx);
|
||||
let open = ChannelView::load(ChannelId(state.channel_id), workspace, cx);
|
||||
|
||||
Some(cx.spawn(|mut cx| async move {
|
||||
let this = open.await?;
|
||||
@@ -556,6 +565,10 @@ impl FollowableItem for ChannelView {
|
||||
})
|
||||
}
|
||||
|
||||
fn leader_peer_id(&self, cx: &AppContext) -> Option<PeerId> {
|
||||
self.editor.read(cx).leader_peer_id(cx)
|
||||
}
|
||||
|
||||
fn is_project_item(&self, _cx: &WindowContext) -> bool {
|
||||
false
|
||||
}
|
||||
@@ -563,6 +576,19 @@ impl FollowableItem for ChannelView {
|
||||
fn to_follow_event(event: &Self::Event) -> Option<workspace::item::FollowEvent> {
|
||||
Editor::to_follow_event(event)
|
||||
}
|
||||
|
||||
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup> {
|
||||
let existing = existing.channel_buffer.read(cx);
|
||||
if self.channel_buffer.read(cx).channel_id == existing.channel_id {
|
||||
if existing.is_connected() {
|
||||
Some(Dedup::KeepExisting)
|
||||
} else {
|
||||
Some(Dedup::ReplaceExisting)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelBufferCollaborationHub(Model<ChannelBuffer>);
|
||||
|
||||
@@ -268,7 +268,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
init_settings(cx);
|
||||
|
||||
workspace::register_project_item::<Editor>(cx);
|
||||
workspace::register_followable_item::<Editor>(cx);
|
||||
workspace::FollowableViewRegistry::register::<Editor>(cx);
|
||||
workspace::register_deserializable_item::<Editor>(cx);
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
@@ -2037,10 +2037,6 @@ impl Editor {
|
||||
self.buffer.read(cx).replica_id()
|
||||
}
|
||||
|
||||
pub fn leader_peer_id(&self) -> Option<PeerId> {
|
||||
self.leader_peer_id
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &Model<MultiBuffer> {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
@@ -8189,7 +8189,6 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
|
||||
let follower_1 = cx
|
||||
.update_window(*workspace.deref(), |_, cx| {
|
||||
Editor::from_state_proto(
|
||||
pane.clone(),
|
||||
workspace.root_view(cx).unwrap(),
|
||||
ViewId {
|
||||
creator: Default::default(),
|
||||
@@ -8281,7 +8280,6 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
|
||||
let follower_2 = cx
|
||||
.update_window(*workspace.deref(), |_, cx| {
|
||||
Editor::from_state_proto(
|
||||
pane.clone(),
|
||||
workspace.root_view(cx).unwrap().clone(),
|
||||
ViewId {
|
||||
creator: Default::default(),
|
||||
|
||||
@@ -19,7 +19,7 @@ use multi_buffer::AnchorRangeExt;
|
||||
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
|
||||
use rpc::proto::{self, update_view, PeerId};
|
||||
use settings::Settings;
|
||||
use workspace::item::{ItemSettings, TabContentParams};
|
||||
use workspace::item::{Dedup, ItemSettings, TabContentParams};
|
||||
|
||||
use std::{
|
||||
any::TypeId,
|
||||
@@ -34,7 +34,7 @@ use text::{BufferId, Selection};
|
||||
use theme::{Theme, ThemeSettings};
|
||||
use ui::{h_flex, prelude::*, Label};
|
||||
use util::{paths::PathExt, ResultExt, TryFutureExt};
|
||||
use workspace::item::{BreadcrumbText, FollowEvent, FollowableItemHandle};
|
||||
use workspace::item::{BreadcrumbText, FollowEvent};
|
||||
use workspace::{
|
||||
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
|
||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||
@@ -49,7 +49,6 @@ impl FollowableItem for Editor {
|
||||
}
|
||||
|
||||
fn from_state_proto(
|
||||
pane: View<workspace::Pane>,
|
||||
workspace: View<Workspace>,
|
||||
remote_id: ViewId,
|
||||
state: &mut Option<proto::view::Variant>,
|
||||
@@ -63,7 +62,6 @@ impl FollowableItem for Editor {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let client = project.read(cx).client();
|
||||
let replica_id = project.read(cx).replica_id();
|
||||
let buffer_ids = state
|
||||
.excerpts
|
||||
@@ -77,72 +75,55 @@ impl FollowableItem for Editor {
|
||||
.collect::<Result<Vec<_>>>()
|
||||
});
|
||||
|
||||
let pane = pane.downgrade();
|
||||
Some(cx.spawn(|mut cx| async move {
|
||||
let mut buffers = futures::future::try_join_all(buffers?)
|
||||
.await
|
||||
.debug_assert_ok("leaders don't share views for unshared buffers")?;
|
||||
|
||||
let editor = pane.update(&mut cx, |pane, cx| {
|
||||
let mut editors = pane.items_of_type::<Self>();
|
||||
editors.find(|editor| {
|
||||
let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
|
||||
let singleton_buffer_matches = state.singleton
|
||||
&& buffers.first()
|
||||
== editor.read(cx).buffer.read(cx).as_singleton().as_ref();
|
||||
ids_match || singleton_buffer_matches
|
||||
let editor = cx.update(|cx| {
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer;
|
||||
if state.singleton && buffers.len() == 1 {
|
||||
multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
|
||||
} else {
|
||||
multibuffer = MultiBuffer::new(replica_id, project.read(cx).capability());
|
||||
let mut excerpts = state.excerpts.into_iter().peekable();
|
||||
while let Some(excerpt) = excerpts.peek() {
|
||||
let Ok(buffer_id) = BufferId::new(excerpt.buffer_id) else {
|
||||
continue;
|
||||
};
|
||||
let buffer_excerpts = iter::from_fn(|| {
|
||||
let excerpt = excerpts.peek()?;
|
||||
(excerpt.buffer_id == u64::from(buffer_id))
|
||||
.then(|| excerpts.next().unwrap())
|
||||
});
|
||||
let buffer =
|
||||
buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
|
||||
if let Some(buffer) = buffer {
|
||||
multibuffer.push_excerpts(
|
||||
buffer.clone(),
|
||||
buffer_excerpts.filter_map(deserialize_excerpt_range),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(title) = &state.title {
|
||||
multibuffer = multibuffer.with_title(title.clone())
|
||||
}
|
||||
|
||||
multibuffer
|
||||
});
|
||||
|
||||
cx.new_view(|cx| {
|
||||
let mut editor =
|
||||
Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx);
|
||||
editor.remote_id = Some(remote_id);
|
||||
editor
|
||||
})
|
||||
})?;
|
||||
|
||||
let editor = if let Some(editor) = editor {
|
||||
editor
|
||||
} else {
|
||||
pane.update(&mut cx, |_, cx| {
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer;
|
||||
if state.singleton && buffers.len() == 1 {
|
||||
multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
|
||||
} else {
|
||||
multibuffer =
|
||||
MultiBuffer::new(replica_id, project.read(cx).capability());
|
||||
let mut excerpts = state.excerpts.into_iter().peekable();
|
||||
while let Some(excerpt) = excerpts.peek() {
|
||||
let Ok(buffer_id) = BufferId::new(excerpt.buffer_id) else {
|
||||
continue;
|
||||
};
|
||||
let buffer_excerpts = iter::from_fn(|| {
|
||||
let excerpt = excerpts.peek()?;
|
||||
(excerpt.buffer_id == u64::from(buffer_id))
|
||||
.then(|| excerpts.next().unwrap())
|
||||
});
|
||||
let buffer =
|
||||
buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
|
||||
if let Some(buffer) = buffer {
|
||||
multibuffer.push_excerpts(
|
||||
buffer.clone(),
|
||||
buffer_excerpts.filter_map(deserialize_excerpt_range),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(title) = &state.title {
|
||||
multibuffer = multibuffer.with_title(title.clone())
|
||||
}
|
||||
|
||||
multibuffer
|
||||
});
|
||||
|
||||
cx.new_view(|cx| {
|
||||
let mut editor =
|
||||
Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx);
|
||||
editor.remote_id = Some(remote_id);
|
||||
editor
|
||||
})
|
||||
})?
|
||||
};
|
||||
|
||||
update_editor_from_message(
|
||||
editor.downgrade(),
|
||||
project,
|
||||
@@ -181,6 +162,10 @@ impl FollowableItem for Editor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn leader_peer_id(&self, _cx: &AppContext) -> Option<PeerId> {
|
||||
self.leader_peer_id.clone()
|
||||
}
|
||||
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
|
||||
let buffer = self.buffer.read(cx);
|
||||
if buffer
|
||||
@@ -327,6 +312,16 @@ impl FollowableItem for Editor {
|
||||
fn is_project_item(&self, _cx: &WindowContext) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup> {
|
||||
let self_singleton = self.buffer.read(cx).as_singleton()?;
|
||||
let other_singleton = existing.buffer.read(cx).as_singleton()?;
|
||||
if self_singleton == other_singleton {
|
||||
Some(Dedup::KeepExisting)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_editor_from_message(
|
||||
|
||||
@@ -41,6 +41,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.iter()
|
||||
.find_map(|item| item.downcast::<ExtensionsPage>());
|
||||
|
||||
if let Some(existing) = existing {
|
||||
|
||||
@@ -291,6 +291,10 @@ pub trait BorrowAppContext {
|
||||
fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
|
||||
where
|
||||
G: Global;
|
||||
/// Updates the global state of the given type, creating a default if it didn't exist before.
|
||||
fn update_default_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
|
||||
where
|
||||
G: Global + Default;
|
||||
}
|
||||
|
||||
impl<C> BorrowAppContext for C
|
||||
@@ -310,6 +314,14 @@ where
|
||||
self.borrow_mut().end_global_lease(global);
|
||||
result
|
||||
}
|
||||
|
||||
fn update_default_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
|
||||
where
|
||||
G: Global + Default,
|
||||
{
|
||||
self.borrow_mut().default_global::<G>();
|
||||
self.update_global(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A flatten equivalent for anyhow `Result`s.
|
||||
|
||||
@@ -1577,18 +1577,16 @@ message Follow {
|
||||
|
||||
message FollowResponse {
|
||||
View active_view = 3;
|
||||
// TODO: after 0.124.0 is retired, remove these.
|
||||
optional ViewId active_view_id = 1;
|
||||
repeated View views = 2;
|
||||
reserved 1;
|
||||
reserved 2;
|
||||
}
|
||||
|
||||
message UpdateFollowers {
|
||||
uint64 room_id = 1;
|
||||
optional uint64 project_id = 2;
|
||||
reserved 3;
|
||||
reserved 5;
|
||||
oneof variant {
|
||||
View create_view = 5;
|
||||
// TODO: after 0.124.0 is retired, remove these.
|
||||
UpdateActiveView update_active_view = 4;
|
||||
UpdateView update_view = 6;
|
||||
}
|
||||
@@ -1616,11 +1614,15 @@ message ViewId {
|
||||
}
|
||||
|
||||
message UpdateActiveView {
|
||||
optional ViewId id = 1;
|
||||
optional PeerId leader_id = 2;
|
||||
reserved 1;
|
||||
reserved 2;
|
||||
View view = 3;
|
||||
}
|
||||
|
||||
enum PanelId {
|
||||
AssistantPanel = 0;
|
||||
}
|
||||
|
||||
message UpdateView {
|
||||
ViewId id = 1;
|
||||
optional PeerId leader_id = 2;
|
||||
@@ -1643,10 +1645,12 @@ message UpdateView {
|
||||
message View {
|
||||
ViewId id = 1;
|
||||
optional PeerId leader_id = 2;
|
||||
optional PanelId panel_id = 6;
|
||||
|
||||
oneof variant {
|
||||
Editor editor = 3;
|
||||
ChannelView channel_view = 4;
|
||||
ContextEditor context_editor = 5;
|
||||
}
|
||||
|
||||
message Editor {
|
||||
@@ -1664,6 +1668,11 @@ message View {
|
||||
uint64 channel_id = 1;
|
||||
Editor editor = 2;
|
||||
}
|
||||
|
||||
message ContextEditor {
|
||||
string context_id = 1;
|
||||
Editor editor = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Collaborator {
|
||||
|
||||
@@ -757,6 +757,7 @@ impl ProjectSearchView {
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.iter()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>());
|
||||
|
||||
Self::existing_or_new_search(workspace, existing, action, cx);
|
||||
|
||||
@@ -194,7 +194,8 @@ impl TabSwitcherDelegate {
|
||||
},
|
||||
);
|
||||
|
||||
let items: Vec<Box<dyn ItemHandle>> = pane.items().map(|item| item.boxed_clone()).collect();
|
||||
let items: Vec<Box<dyn ItemHandle>> =
|
||||
pane.items().iter().map(|item| item.boxed_clone()).collect();
|
||||
items
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
||||
@@ -456,6 +456,7 @@ impl TerminalPanel {
|
||||
self.pane
|
||||
.read(cx)
|
||||
.items()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, item)| Some((index, item.act_as::<TerminalView>(cx)?)))
|
||||
.filter_map(|(index, terminal_view)| {
|
||||
@@ -545,6 +546,7 @@ impl TerminalPanel {
|
||||
.pane
|
||||
.read(cx)
|
||||
.items()
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
let terminal_view = item.act_as::<TerminalView>(cx)?;
|
||||
if terminal_view.read(cx).terminal().read(cx).task().is_some() {
|
||||
|
||||
@@ -46,7 +46,7 @@ use std::{ops::Range, sync::Arc};
|
||||
use surrounds::{add_surrounds, change_surrounds, delete_surrounds, SurroundsType};
|
||||
use ui::BorrowAppContext;
|
||||
use visual::{visual_block_motion, visual_replace};
|
||||
use workspace::{self, Workspace};
|
||||
use workspace::{self, item::FollowableItem, Workspace};
|
||||
|
||||
use crate::state::ReplayableAction;
|
||||
|
||||
@@ -241,7 +241,7 @@ impl Vim {
|
||||
self.active_editor = Some(editor.clone().downgrade());
|
||||
self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
|
||||
EditorEvent::SelectionsChanged { local: true } => {
|
||||
if editor.read(cx).leader_peer_id().is_none() {
|
||||
if editor.read(cx).leader_peer_id(cx).is_none() {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.local_selections_changed(editor, cx);
|
||||
})
|
||||
@@ -275,7 +275,7 @@ impl Vim {
|
||||
&& !newest_selection_empty
|
||||
&& self.state().mode == Mode::Normal
|
||||
// When following someone, don't switch vim mode.
|
||||
&& editor.leader_peer_id().is_none()
|
||||
&& editor.leader_peer_id(cx).is_none()
|
||||
{
|
||||
self.switch_mode(Mode::Visual, true, cx);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::item::{FollowableItemHandle, ItemHandle};
|
||||
use crate::persistence::model::DockData;
|
||||
use crate::{status_bar::StatusItemView, Workspace};
|
||||
use crate::{DraggedDock, Event};
|
||||
use call::ActiveCall;
|
||||
use client::proto::{self, PeerId};
|
||||
use gpui::{
|
||||
deferred, div, px, Action, AnchorCorner, AnyView, AppContext, Axis, Entity, EntityId,
|
||||
EventEmitter, FocusHandle, FocusableView, IntoElement, KeyContext, MouseButton, MouseDownEvent,
|
||||
@@ -44,6 +47,29 @@ pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
|
||||
}
|
||||
fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
|
||||
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
|
||||
fn active_item(&self, _cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
|
||||
None
|
||||
}
|
||||
fn items<'a>(&'a self, _cx: &'a AppContext) -> &'a [Box<dyn ItemHandle>] {
|
||||
&[]
|
||||
}
|
||||
fn remote_id() -> Option<proto::PanelId> {
|
||||
None
|
||||
}
|
||||
fn leader_updated(
|
||||
&mut self,
|
||||
_view: Box<dyn FollowableItemHandle>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
unimplemented!("leader_updated was not implemented")
|
||||
}
|
||||
fn dedup(
|
||||
&mut self,
|
||||
_view: Box<dyn FollowableItemHandle>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Box<dyn FollowableItemHandle> {
|
||||
unimplemented!("dedup was not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PanelHandle: Send + Sync {
|
||||
@@ -55,6 +81,15 @@ pub trait PanelHandle: Send + Sync {
|
||||
fn is_zoomed(&self, cx: &WindowContext) -> bool;
|
||||
fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
|
||||
fn set_active(&self, active: bool, cx: &mut WindowContext);
|
||||
fn active_item(&self, cx: &WindowContext) -> Option<Box<dyn ItemHandle>>;
|
||||
fn items<'a>(&'a self, cx: &'a WindowContext) -> &'a [Box<dyn ItemHandle>];
|
||||
fn dedup(
|
||||
&self,
|
||||
item: Box<dyn FollowableItemHandle>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Box<dyn FollowableItemHandle>;
|
||||
fn remote_id(&self) -> Option<proto::PanelId>;
|
||||
fn leader_updated(&self, view: Box<dyn FollowableItemHandle>, cx: &mut WindowContext);
|
||||
fn size(&self, cx: &WindowContext) -> Pixels;
|
||||
fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);
|
||||
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName>;
|
||||
@@ -101,6 +136,30 @@ where
|
||||
self.update(cx, |this, cx| this.set_active(active, cx))
|
||||
}
|
||||
|
||||
fn active_item(&self, cx: &WindowContext) -> Option<Box<dyn ItemHandle>> {
|
||||
self.read(cx).active_item(cx)
|
||||
}
|
||||
|
||||
fn items<'a>(&'a self, cx: &'a WindowContext) -> &'a [Box<dyn ItemHandle>] {
|
||||
self.read(cx).items(cx)
|
||||
}
|
||||
|
||||
fn remote_id(&self) -> Option<proto::PanelId> {
|
||||
T::remote_id()
|
||||
}
|
||||
|
||||
fn leader_updated(&self, view: Box<dyn FollowableItemHandle>, cx: &mut WindowContext) {
|
||||
self.update(cx, |this, cx| this.leader_updated(view, cx))
|
||||
}
|
||||
|
||||
fn dedup(
|
||||
&self,
|
||||
item: Box<dyn FollowableItemHandle>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Box<dyn FollowableItemHandle> {
|
||||
self.update(cx, |this, cx| this.dedup(item, cx))
|
||||
}
|
||||
|
||||
fn size(&self, cx: &WindowContext) -> Pixels {
|
||||
self.read(cx).size(cx)
|
||||
}
|
||||
@@ -280,6 +339,11 @@ impl Dock {
|
||||
.find_map(|entry| entry.panel.to_any().clone().downcast().ok())
|
||||
}
|
||||
|
||||
pub fn panel_for_index(&self, index: usize) -> Option<&dyn PanelHandle> {
|
||||
let entry = self.panel_entries.get(index)?;
|
||||
Some(entry.panel.as_ref())
|
||||
}
|
||||
|
||||
pub fn panel_index_for_type<T: Panel>(&self) -> Option<usize> {
|
||||
self.panel_entries
|
||||
.iter()
|
||||
@@ -296,6 +360,12 @@ impl Dock {
|
||||
.position(|entry| entry.panel.persistent_name() == ui_name)
|
||||
}
|
||||
|
||||
pub fn panel_index_for_remote_id(&self, remote_id: proto::PanelId) -> Option<usize> {
|
||||
self.panel_entries
|
||||
.iter()
|
||||
.position(|entry| entry.panel.remote_id() == Some(remote_id))
|
||||
}
|
||||
|
||||
pub fn active_panel_index(&self) -> usize {
|
||||
self.active_panel_index
|
||||
}
|
||||
@@ -508,6 +578,17 @@ impl Dock {
|
||||
Some(&self.panel_entries.get(self.active_panel_index)?.panel)
|
||||
}
|
||||
|
||||
pub fn leader_peer_id(&self, cx: &WindowContext) -> Option<PeerId> {
|
||||
if self.focus_handle.contains_focused(cx) {
|
||||
let panel = self.active_panel()?;
|
||||
let item = panel.active_item(cx)?;
|
||||
let view = item.to_followable_item_handle(cx)?;
|
||||
view.leader_peer_id(cx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn visible_entry(&self) -> Option<&PanelEntry> {
|
||||
if self.is_open {
|
||||
self.panel_entries.get(self.active_panel_index)
|
||||
@@ -627,6 +708,26 @@ impl Render for Dock {
|
||||
}
|
||||
};
|
||||
|
||||
let leader_border = self.leader_peer_id(cx).and_then(|peer_id| {
|
||||
let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
|
||||
let leader = room.remote_participant_for_peer_id(peer_id)?;
|
||||
let mut leader_color = cx
|
||||
.theme()
|
||||
.players()
|
||||
.color_for_participant(leader.participant_index.0)
|
||||
.cursor;
|
||||
leader_color.fade_out(0.3);
|
||||
Some(
|
||||
div()
|
||||
.absolute()
|
||||
.size_full()
|
||||
.left_0()
|
||||
.top_0()
|
||||
.border_2()
|
||||
.border_color(leader_color),
|
||||
)
|
||||
});
|
||||
|
||||
div()
|
||||
.key_context(dispatch_context)
|
||||
.track_focus(&self.focus_handle)
|
||||
@@ -656,6 +757,7 @@ impl Render for Dock {
|
||||
.cached(StyleRefinement::default().v_flex().size_full()),
|
||||
),
|
||||
)
|
||||
.children(leader_border)
|
||||
.when(self.resizeable, |this| this.child(create_resize_handle()))
|
||||
} else {
|
||||
div()
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
persistence::model::ItemId,
|
||||
searchable::SearchableItemHandle,
|
||||
workspace_settings::{AutosaveSetting, WorkspaceSettings},
|
||||
DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation,
|
||||
DelayedDebouncedEditAction, FollowableViewRegistry, ItemNavHistory, ToolbarItemLocation,
|
||||
ViewId, Workspace, WorkspaceId,
|
||||
};
|
||||
use anyhow::Result;
|
||||
@@ -472,22 +472,6 @@ impl<T: Item> ItemHandle for View<T> {
|
||||
this.added_to_workspace(workspace, cx);
|
||||
});
|
||||
|
||||
if let Some(followed_item) = self.to_followable_item_handle(cx) {
|
||||
if let Some(message) = followed_item.to_state_proto(cx) {
|
||||
workspace.update_followers(
|
||||
followed_item.is_project_item(cx),
|
||||
proto::update_followers::Variant::CreateView(proto::View {
|
||||
id: followed_item
|
||||
.remote_id(&workspace.client(), cx)
|
||||
.map(|id| id.to_proto()),
|
||||
variant: Some(message),
|
||||
leader_id: workspace.leader_for_pane(&pane),
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if workspace
|
||||
.panes_by_item
|
||||
.insert(self.item_id(), pane.downgrade())
|
||||
@@ -682,9 +666,7 @@ impl<T: Item> ItemHandle for View<T> {
|
||||
}
|
||||
|
||||
fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
|
||||
let builders = cx.try_global::<FollowableItemBuilders>()?;
|
||||
let item = self.to_any();
|
||||
Some(builders.get(&item.entity_type())?.1(&item))
|
||||
FollowableViewRegistry::to_followable_view(self.clone(), cx)
|
||||
}
|
||||
|
||||
fn on_release(
|
||||
@@ -769,11 +751,15 @@ pub enum FollowEvent {
|
||||
Unfollow,
|
||||
}
|
||||
|
||||
pub enum Dedup {
|
||||
KeepExisting,
|
||||
ReplaceExisting,
|
||||
}
|
||||
|
||||
pub trait FollowableItem: Item {
|
||||
fn remote_id(&self) -> Option<ViewId>;
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
|
||||
fn from_state_proto(
|
||||
pane: View<Pane>,
|
||||
project: View<Workspace>,
|
||||
id: ViewId,
|
||||
state: &mut Option<proto::view::Variant>,
|
||||
@@ -794,12 +780,16 @@ pub trait FollowableItem: Item {
|
||||
) -> Task<Result<()>>;
|
||||
fn is_project_item(&self, cx: &WindowContext) -> bool;
|
||||
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
|
||||
fn leader_peer_id(&self, cx: &AppContext) -> Option<PeerId>;
|
||||
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup>;
|
||||
}
|
||||
|
||||
pub trait FollowableItemHandle: ItemHandle {
|
||||
fn boxed_clone(&self) -> Box<dyn FollowableItemHandle>;
|
||||
fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId>;
|
||||
fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle>;
|
||||
fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
|
||||
fn leader_peer_id(&self, cx: &AppContext) -> Option<PeerId>;
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
|
||||
fn add_event_to_update_proto(
|
||||
&self,
|
||||
@@ -815,9 +805,14 @@ pub trait FollowableItemHandle: ItemHandle {
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<()>>;
|
||||
fn is_project_item(&self, cx: &WindowContext) -> bool;
|
||||
fn dedup(&self, existing: &dyn FollowableItemHandle, cx: &WindowContext) -> Option<Dedup>;
|
||||
}
|
||||
|
||||
impl<T: FollowableItem> FollowableItemHandle for View<T> {
|
||||
fn boxed_clone(&self) -> Box<dyn FollowableItemHandle> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId> {
|
||||
self.read(cx).remote_id().or_else(|| {
|
||||
client.peer_id().map(|creator| ViewId {
|
||||
@@ -835,6 +830,10 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
|
||||
self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
|
||||
}
|
||||
|
||||
fn leader_peer_id(&self, cx: &AppContext) -> Option<PeerId> {
|
||||
self.read(cx).leader_peer_id(cx)
|
||||
}
|
||||
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
|
||||
self.read(cx).to_state_proto(cx)
|
||||
}
|
||||
@@ -868,6 +867,11 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
|
||||
fn is_project_item(&self, cx: &WindowContext) -> bool {
|
||||
self.read(cx).is_project_item(cx)
|
||||
}
|
||||
|
||||
fn dedup(&self, existing: &dyn FollowableItemHandle, cx: &WindowContext) -> Option<Dedup> {
|
||||
let existing = existing.to_any().downcast::<T>().ok()?;
|
||||
self.read(cx).dedup(existing.read(cx), cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WeakFollowableItemHandle: Send + Sync {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
item::{
|
||||
ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings, TabContentParams,
|
||||
WeakItemHandle,
|
||||
self, ClosePosition, FollowableItemHandle, Item, ItemHandle, ItemSettings,
|
||||
PreviewTabsSettings, TabContentParams, WeakItemHandle,
|
||||
},
|
||||
toolbar::Toolbar,
|
||||
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
|
||||
@@ -452,6 +452,7 @@ impl Pane {
|
||||
if let Some(alternative) = alternative {
|
||||
let existing = self
|
||||
.items()
|
||||
.iter()
|
||||
.find_position(|item| item.item_id() == alternative.id());
|
||||
if let Some((ix, _)) = existing {
|
||||
self.activate_item(ix, true, true, cx);
|
||||
@@ -859,12 +860,42 @@ impl Pane {
|
||||
cx.emit(Event::AddItem { item });
|
||||
}
|
||||
|
||||
pub fn dedup(
|
||||
&mut self,
|
||||
new_item: Box<dyn FollowableItemHandle>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Box<dyn FollowableItemHandle> {
|
||||
for (ix, item) in self.items().iter().enumerate() {
|
||||
if let Some(item) = item.to_followable_item_handle(cx) {
|
||||
match new_item.dedup(item.as_ref(), cx) {
|
||||
Some(item::Dedup::KeepExisting) => {
|
||||
return item.to_followable_item_handle(cx).unwrap();
|
||||
}
|
||||
Some(item::Dedup::ReplaceExisting) => {
|
||||
self.remove_item(ix, false, false, cx);
|
||||
self.add_item(
|
||||
ItemHandle::boxed_clone(new_item.as_ref()),
|
||||
false,
|
||||
false,
|
||||
Some(ix),
|
||||
cx,
|
||||
);
|
||||
return new_item;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_item
|
||||
}
|
||||
|
||||
pub fn items_len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
pub fn items(&self) -> impl DoubleEndedIterator<Item = &Box<dyn ItemHandle>> {
|
||||
self.items.iter()
|
||||
pub fn items(&self) -> &[Box<dyn ItemHandle>] {
|
||||
&self.items
|
||||
}
|
||||
|
||||
pub fn items_of_type<T: Render>(&self) -> impl '_ + Iterator<Item = View<T>> {
|
||||
@@ -1040,6 +1071,7 @@ impl Pane {
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let item_ids: Vec<_> = self
|
||||
.items()
|
||||
.iter()
|
||||
.filter(|item| !item.is_dirty(cx))
|
||||
.map(|item| item.item_id())
|
||||
.collect();
|
||||
@@ -1067,6 +1099,7 @@ impl Pane {
|
||||
) -> Task<Result<()>> {
|
||||
let item_ids: Vec<_> = self
|
||||
.items()
|
||||
.iter()
|
||||
.take_while(|item| item.item_id() != item_id)
|
||||
.map(|item| item.item_id())
|
||||
.collect();
|
||||
@@ -1094,6 +1127,7 @@ impl Pane {
|
||||
) -> Task<Result<()>> {
|
||||
let item_ids: Vec<_> = self
|
||||
.items()
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while(|item| item.item_id() != item_id)
|
||||
.map(|item| item.item_id())
|
||||
@@ -1527,13 +1561,14 @@ impl Pane {
|
||||
entry_id: ProjectEntryId,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
) -> Option<()> {
|
||||
let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
|
||||
if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
|
||||
Some((i, item.item_id()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
let (item_index_to_delete, item_id) =
|
||||
self.items().iter().enumerate().find_map(|(i, item)| {
|
||||
if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
|
||||
Some((i, item.item_id()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
self.remove_item(item_index_to_delete, false, true, cx);
|
||||
self.nav_history.remove_item(item_id);
|
||||
|
||||
@@ -81,9 +81,9 @@ use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
|
||||
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
||||
pub use ui;
|
||||
use ui::{
|
||||
div, h_flex, px, Context as _, Div, FluentBuilder, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _,
|
||||
WindowContext,
|
||||
div, h_flex, px, BorrowAppContext, Context as _, Div, FluentBuilder, InteractiveElement as _,
|
||||
IntoElement, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext,
|
||||
VisualContext as _, WindowContext,
|
||||
};
|
||||
use util::{maybe, ResultExt};
|
||||
use uuid::Uuid;
|
||||
@@ -356,41 +356,59 @@ pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
type FollowableItemBuilder = fn(
|
||||
View<Pane>,
|
||||
View<Workspace>,
|
||||
ViewId,
|
||||
&mut Option<proto::view::Variant>,
|
||||
&mut WindowContext,
|
||||
) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
|
||||
#[derive(Default)]
|
||||
pub struct FollowableViewRegistry(HashMap<TypeId, FollowableViewDescriptor>);
|
||||
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
struct FollowableItemBuilders(
|
||||
HashMap<
|
||||
TypeId,
|
||||
(
|
||||
FollowableItemBuilder,
|
||||
fn(&AnyView) -> Box<dyn FollowableItemHandle>,
|
||||
),
|
||||
>,
|
||||
);
|
||||
struct FollowableViewDescriptor {
|
||||
from_state_proto: fn(
|
||||
View<Workspace>,
|
||||
ViewId,
|
||||
&mut Option<proto::view::Variant>,
|
||||
&mut WindowContext,
|
||||
) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>,
|
||||
to_followable_view: fn(&AnyView) -> Box<dyn FollowableItemHandle>,
|
||||
}
|
||||
|
||||
impl Global for FollowableItemBuilders {}
|
||||
impl Global for FollowableViewRegistry {}
|
||||
|
||||
pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
|
||||
let builders = cx.default_global::<FollowableItemBuilders>();
|
||||
builders.insert(
|
||||
TypeId::of::<I>(),
|
||||
(
|
||||
|pane, workspace, id, state, cx| {
|
||||
I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
|
||||
cx.foreground_executor()
|
||||
.spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
|
||||
})
|
||||
impl FollowableViewRegistry {
|
||||
pub fn register<I: FollowableItem>(cx: &mut AppContext) {
|
||||
cx.default_global::<Self>().0.insert(
|
||||
TypeId::of::<I>(),
|
||||
FollowableViewDescriptor {
|
||||
from_state_proto: |workspace, id, state, cx| {
|
||||
I::from_state_proto(workspace, id, state, cx).map(|task| {
|
||||
cx.foreground_executor()
|
||||
.spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
|
||||
})
|
||||
},
|
||||
to_followable_view: |view| Box::new(view.clone().downcast::<I>().unwrap()),
|
||||
},
|
||||
|this| Box::new(this.clone().downcast::<I>().unwrap()),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
pub fn from_state_proto(
|
||||
workspace: View<Workspace>,
|
||||
view_id: ViewId,
|
||||
mut state: Option<proto::view::Variant>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>> {
|
||||
cx.update_default_global(|this: &mut Self, cx| {
|
||||
this.0.values().find_map(|descriptor| {
|
||||
(descriptor.from_state_proto)(workspace.clone(), view_id, &mut state, cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_followable_view(
|
||||
view: impl Into<AnyView>,
|
||||
cx: &AppContext,
|
||||
) -> Option<Box<dyn FollowableItemHandle>> {
|
||||
let this = cx.try_global::<Self>()?;
|
||||
let view = view.into();
|
||||
let descriptor = this.0.get(&view.entity_type())?;
|
||||
Some((descriptor.to_followable_view)(&view))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
@@ -628,7 +646,12 @@ pub struct ViewId {
|
||||
struct FollowerState {
|
||||
leader_id: PeerId,
|
||||
active_view_id: Option<ViewId>,
|
||||
items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
|
||||
items_by_leader_view_id: HashMap<ViewId, FollowerView>,
|
||||
}
|
||||
|
||||
struct FollowerView {
|
||||
view: Box<dyn FollowableItemHandle>,
|
||||
location: Option<proto::PanelId>,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
@@ -1515,7 +1538,7 @@ impl Workspace {
|
||||
.panes
|
||||
.iter()
|
||||
.flat_map(|pane| {
|
||||
pane.read(cx).items().filter_map(|item| {
|
||||
pane.read(cx).items().iter().filter_map(|item| {
|
||||
if item.is_dirty(cx) {
|
||||
Some((pane.downgrade(), item.boxed_clone()))
|
||||
} else {
|
||||
@@ -1950,9 +1973,7 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
|
||||
|
||||
for dock in docks {
|
||||
for dock in self.docks() {
|
||||
dock.update(cx, |dock, cx| {
|
||||
dock.set_open(false, cx);
|
||||
});
|
||||
@@ -1963,6 +1984,10 @@ impl Workspace {
|
||||
self.serialize_workspace(cx);
|
||||
}
|
||||
|
||||
fn docks(&self) -> [&View<Dock>; 3] {
|
||||
[&self.left_dock, &self.bottom_dock, &self.right_dock]
|
||||
}
|
||||
|
||||
/// Transfer focus to the panel of the given type.
|
||||
pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
|
||||
let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
|
||||
@@ -1985,7 +2010,7 @@ impl Workspace {
|
||||
) -> Option<Arc<dyn PanelHandle>> {
|
||||
let mut result_panel = None;
|
||||
let mut serialize = false;
|
||||
for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
|
||||
for dock in self.docks() {
|
||||
if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
|
||||
let mut focus_center = false;
|
||||
let panel = dock.update(cx, |dock, cx| {
|
||||
@@ -2023,7 +2048,7 @@ impl Workspace {
|
||||
|
||||
/// Open the panel of the given type
|
||||
pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
|
||||
for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
|
||||
for dock in self.docks() {
|
||||
if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
|
||||
dock.update(cx, |dock, cx| {
|
||||
dock.activate_panel(panel_index, cx);
|
||||
@@ -2034,7 +2059,7 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
|
||||
for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
|
||||
for dock in self.docks() {
|
||||
let dock = dock.read(cx);
|
||||
if let Some(panel) = dock.panel::<T>() {
|
||||
return Some(panel);
|
||||
@@ -2057,7 +2082,7 @@ impl Workspace {
|
||||
|
||||
// If another dock is zoomed, hide it.
|
||||
let mut focus_center = false;
|
||||
for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
|
||||
for dock in self.docks() {
|
||||
dock.update(cx, |dock, cx| {
|
||||
if Some(dock.position()) != dock_to_reveal {
|
||||
if let Some(panel) = dock.active_panel() {
|
||||
@@ -2716,6 +2741,7 @@ impl Workspace {
|
||||
let Some((item_ix, item_handle)) = source
|
||||
.read(cx)
|
||||
.items()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
|
||||
else {
|
||||
@@ -2779,7 +2805,7 @@ impl Workspace {
|
||||
self.follower_states.retain(|_, state| {
|
||||
if state.leader_id == peer_id {
|
||||
for item in state.items_by_leader_view_id.values() {
|
||||
item.set_leader_peer_id(None, cx);
|
||||
item.view.set_leader_peer_id(None, cx);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
@@ -2824,25 +2850,16 @@ impl Workspace {
|
||||
.follower_states
|
||||
.get_mut(&pane)
|
||||
.ok_or_else(|| anyhow!("following interrupted"))?;
|
||||
state.active_view_id = if let Some(active_view_id) = response.active_view_id {
|
||||
Some(ViewId::from_proto(active_view_id)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state.active_view_id = response
|
||||
.active_view
|
||||
.as_ref()
|
||||
.and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})??;
|
||||
if let Some(view) = response.active_view {
|
||||
Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, &mut cx)
|
||||
.await?;
|
||||
}
|
||||
Self::add_views_from_leader(
|
||||
this.clone(),
|
||||
leader_id,
|
||||
vec![pane],
|
||||
response.views,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
|
||||
Ok(())
|
||||
}))
|
||||
@@ -2935,7 +2952,7 @@ impl Workspace {
|
||||
let state = self.follower_states.remove(pane)?;
|
||||
let leader_id = state.leader_id;
|
||||
for (_, item) in state.items_by_leader_view_id {
|
||||
item.set_leader_peer_id(None, cx);
|
||||
item.view.set_leader_peer_id(None, cx);
|
||||
}
|
||||
|
||||
if self
|
||||
@@ -3060,7 +3077,8 @@ impl Workspace {
|
||||
follower_project_id: Option<u64>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<proto::View> {
|
||||
let item = self.active_item(cx)?;
|
||||
let (item, panel_id) = self.active_item_for_followers(cx);
|
||||
let item = item?;
|
||||
let leader_id = self
|
||||
.pane_for(&*item)
|
||||
.and_then(|pane| self.leader_for_pane(&pane));
|
||||
@@ -3080,6 +3098,7 @@ impl Workspace {
|
||||
id: Some(id.to_proto()),
|
||||
leader_id,
|
||||
variant: Some(variant),
|
||||
panel_id: panel_id.map(|id| id as i32),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3088,52 +3107,9 @@ impl Workspace {
|
||||
follower_project_id: Option<u64>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> proto::FollowResponse {
|
||||
let client = &self.app_state.client;
|
||||
let project_id = self.project.read(cx).remote_id();
|
||||
|
||||
let active_view = self.active_view_for_follower(follower_project_id, cx);
|
||||
let active_view_id = active_view.as_ref().and_then(|view| view.id.clone());
|
||||
|
||||
cx.notify();
|
||||
|
||||
proto::FollowResponse {
|
||||
active_view,
|
||||
// TODO: once v0.124.0 is retired we can stop sending these
|
||||
active_view_id,
|
||||
views: self
|
||||
.panes()
|
||||
.iter()
|
||||
.flat_map(|pane| {
|
||||
let leader_id = self.leader_for_pane(pane);
|
||||
pane.read(cx).items().filter_map({
|
||||
let cx = &cx;
|
||||
move |item| {
|
||||
let item = item.to_followable_item_handle(cx)?;
|
||||
|
||||
// If the item belongs to a particular project, then it should
|
||||
// only be included if this project is shared, and the follower
|
||||
// is in the project.
|
||||
//
|
||||
// Some items, like channel notes, do not belong to a particular
|
||||
// project, so they should be included regardless of whether the
|
||||
// current project is shared, or what project the follower is in.
|
||||
if item.is_project_item(cx)
|
||||
&& (project_id.is_none() || project_id != follower_project_id)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let id = item.remote_id(client, cx)?.to_proto();
|
||||
let variant = item.to_state_proto(cx)?;
|
||||
Some(proto::View {
|
||||
id: Some(id),
|
||||
leader_id,
|
||||
variant: Some(variant),
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
active_view: self.active_view_for_follower(follower_project_id, cx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3163,12 +3139,10 @@ impl Workspace {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.active_view_id =
|
||||
if let Some(active_view_id) = update_active_view.id.clone() {
|
||||
Some(ViewId::from_proto(active_view_id)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state.active_view_id = update_active_view
|
||||
.view
|
||||
.as_ref()
|
||||
.and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
|
||||
|
||||
if state.active_view_id.is_some_and(|view_id| {
|
||||
!state.items_by_leader_view_id.contains_key(&view_id)
|
||||
@@ -3200,7 +3174,11 @@ impl Workspace {
|
||||
if state.leader_id == leader_id {
|
||||
let view_id = ViewId::from_proto(id.clone())?;
|
||||
if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
|
||||
tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
|
||||
tasks.push(item.view.apply_update_proto(
|
||||
&project,
|
||||
variant.clone(),
|
||||
cx,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3208,16 +3186,6 @@ impl Workspace {
|
||||
})??;
|
||||
try_join_all(tasks).await.log_err();
|
||||
}
|
||||
proto::update_followers::Variant::CreateView(view) => {
|
||||
let panes = this.update(cx, |this, _| {
|
||||
this.follower_states
|
||||
.iter()
|
||||
.filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
|
||||
.cloned()
|
||||
.collect()
|
||||
})?;
|
||||
Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
|
||||
}
|
||||
}
|
||||
this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
|
||||
Ok(())
|
||||
@@ -3232,40 +3200,90 @@ impl Workspace {
|
||||
) -> Result<()> {
|
||||
let this = this.upgrade().context("workspace dropped")?;
|
||||
|
||||
let item_builders = cx.update(|cx| {
|
||||
cx.default_global::<FollowableItemBuilders>()
|
||||
.values()
|
||||
.map(|b| b.0)
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
let Some(id) = view.id.clone() else {
|
||||
return Err(anyhow!("no id for view"));
|
||||
};
|
||||
let id = ViewId::from_proto(id)?;
|
||||
let panel_id = view.panel_id.and_then(|id| proto::PanelId::from_i32(id));
|
||||
|
||||
let mut variant = view.variant.clone();
|
||||
if variant.is_none() {
|
||||
Err(anyhow!("missing view variant"))?;
|
||||
}
|
||||
|
||||
let task = item_builders.iter().find_map(|build_item| {
|
||||
cx.update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx))
|
||||
.log_err()
|
||||
.flatten()
|
||||
});
|
||||
let Some(task) = task else {
|
||||
return Err(anyhow!(
|
||||
"failed to construct view from leader (maybe from a different version of zed?)"
|
||||
));
|
||||
let existing_item = if let Some(panel_id) = panel_id {
|
||||
this.update(cx, |this, cx| {
|
||||
let client = this.client().clone();
|
||||
for dock in this.docks() {
|
||||
if let Some(panel_ix) = dock.read(cx).panel_index_for_remote_id(panel_id) {
|
||||
let panel = dock.read(cx).panel_for_index(panel_ix)?;
|
||||
return panel.items(cx).iter().find_map(|item| {
|
||||
let item = item.to_followable_item_handle(cx)?;
|
||||
if item.remote_id(&client, cx) == Some(id) {
|
||||
Some(item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
})?
|
||||
} else {
|
||||
pane.update(cx, |pane, cx| {
|
||||
let client = this.read(cx).client().clone();
|
||||
pane.items().iter().find_map(|item| {
|
||||
let item = item.to_followable_item_handle(cx)?;
|
||||
if item.remote_id(&client, cx) == Some(id) {
|
||||
Some(item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})?
|
||||
};
|
||||
|
||||
let item = task.await?;
|
||||
let item = if let Some(existing_item) = existing_item {
|
||||
existing_item
|
||||
} else {
|
||||
let variant = view.variant.clone();
|
||||
if variant.is_none() {
|
||||
Err(anyhow!("missing view variant"))?;
|
||||
}
|
||||
|
||||
let task = cx.update(|cx| {
|
||||
FollowableViewRegistry::from_state_proto(this.clone(), id, variant, cx)
|
||||
})?;
|
||||
|
||||
let Some(task) = task else {
|
||||
return Err(anyhow!(
|
||||
"failed to construct view from leader (maybe from a different version of zed?)"
|
||||
));
|
||||
};
|
||||
|
||||
let new_item = task.await?;
|
||||
if let Some(panel_id) = panel_id {
|
||||
this.update(cx, |this, cx| {
|
||||
for dock in this.docks() {
|
||||
if let Some(panel_ix) = dock.read(cx).panel_index_for_remote_id(panel_id) {
|
||||
return dock.update(cx, |dock, cx| {
|
||||
let panel = dock.panel_for_index(panel_ix).unwrap();
|
||||
panel.dedup(new_item, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
new_item
|
||||
})?
|
||||
} else {
|
||||
pane.update(cx, |pane, cx| pane.dedup(new_item, cx))?
|
||||
}
|
||||
};
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
let state = this.follower_states.get_mut(&pane)?;
|
||||
item.set_leader_peer_id(Some(leader_id), cx);
|
||||
state.items_by_leader_view_id.insert(id, item);
|
||||
state.items_by_leader_view_id.insert(
|
||||
id,
|
||||
FollowerView {
|
||||
view: item,
|
||||
location: panel_id,
|
||||
},
|
||||
);
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
@@ -3273,74 +3291,13 @@ impl Workspace {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_views_from_leader(
|
||||
this: WeakView<Self>,
|
||||
leader_id: PeerId,
|
||||
panes: Vec<View<Pane>>,
|
||||
views: Vec<proto::View>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
let this = this.upgrade().context("workspace dropped")?;
|
||||
|
||||
let item_builders = cx.update(|cx| {
|
||||
cx.default_global::<FollowableItemBuilders>()
|
||||
.values()
|
||||
.map(|b| b.0)
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
let mut item_tasks_by_pane = HashMap::default();
|
||||
for pane in panes {
|
||||
let mut item_tasks = Vec::new();
|
||||
let mut leader_view_ids = Vec::new();
|
||||
for view in &views {
|
||||
let Some(id) = &view.id else {
|
||||
continue;
|
||||
};
|
||||
let id = ViewId::from_proto(id.clone())?;
|
||||
let mut variant = view.variant.clone();
|
||||
if variant.is_none() {
|
||||
Err(anyhow!("missing view variant"))?;
|
||||
}
|
||||
for build_item in &item_builders {
|
||||
let task = cx.update(|cx| {
|
||||
build_item(pane.clone(), this.clone(), id, &mut variant, cx)
|
||||
})?;
|
||||
if let Some(task) = task {
|
||||
item_tasks.push(task);
|
||||
leader_view_ids.push(id);
|
||||
break;
|
||||
} else if variant.is_none() {
|
||||
Err(anyhow!(
|
||||
"failed to construct view from leader (maybe from a different version of zed?)"
|
||||
))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
|
||||
}
|
||||
|
||||
for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
|
||||
let items = futures::future::try_join_all(item_tasks).await?;
|
||||
this.update(cx, |this, cx| {
|
||||
let state = this.follower_states.get_mut(&pane)?;
|
||||
for (id, item) in leader_view_ids.into_iter().zip(items) {
|
||||
item.set_leader_peer_id(Some(leader_id), cx);
|
||||
state.items_by_leader_view_id.insert(id, item);
|
||||
}
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
|
||||
let mut is_project_item = true;
|
||||
let mut update = proto::UpdateActiveView::default();
|
||||
if cx.is_window_active() {
|
||||
if let Some(item) = self.active_item(cx) {
|
||||
let (active_item, panel_id) = self.active_item_for_followers(cx);
|
||||
|
||||
if let Some(item) = active_item {
|
||||
if item.focus_handle(cx).contains_focused(cx) {
|
||||
let leader_id = self
|
||||
.pane_for(&*item)
|
||||
@@ -3357,15 +3314,11 @@ impl Workspace {
|
||||
id: Some(id.clone()),
|
||||
leader_id,
|
||||
variant: Some(variant),
|
||||
panel_id: panel_id.map(|id| id as i32),
|
||||
});
|
||||
|
||||
is_project_item = item.is_project_item(cx);
|
||||
update = proto::UpdateActiveView {
|
||||
view,
|
||||
// TODO: once v0.124.0 is retired we can stop sending these
|
||||
id: Some(id),
|
||||
leader_id,
|
||||
};
|
||||
update = proto::UpdateActiveView { view };
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -3373,8 +3326,9 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
if &update.id != &self.last_active_view_id {
|
||||
self.last_active_view_id.clone_from(&update.id);
|
||||
let active_view_id = update.view.as_ref().and_then(|view| view.id.as_ref());
|
||||
if active_view_id != self.last_active_view_id.as_ref() {
|
||||
self.last_active_view_id = active_view_id.cloned();
|
||||
self.update_followers(
|
||||
is_project_item,
|
||||
proto::update_followers::Variant::UpdateActiveView(update),
|
||||
@@ -3383,6 +3337,30 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
fn active_item_for_followers(
|
||||
&self,
|
||||
cx: &mut WindowContext,
|
||||
) -> (Option<Box<dyn ItemHandle>>, Option<proto::PanelId>) {
|
||||
let mut active_item = None;
|
||||
let mut panel_id = None;
|
||||
for dock in self.docks() {
|
||||
if dock.focus_handle(cx).contains_focused(cx) {
|
||||
if let Some(panel) = dock.read(cx).active_panel() {
|
||||
if let Some(item) = panel.active_item(cx) {
|
||||
active_item = Some(item);
|
||||
panel_id = panel.remote_id();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if active_item.is_none() {
|
||||
active_item = self.active_pane().read(cx).active_item();
|
||||
}
|
||||
(active_item, panel_id)
|
||||
}
|
||||
|
||||
fn update_followers(
|
||||
&self,
|
||||
project_only: bool,
|
||||
@@ -3413,7 +3391,6 @@ impl Workspace {
|
||||
let call = self.active_call()?;
|
||||
let room = call.read(cx).room()?.read(cx);
|
||||
let participant = room.remote_participant_for_peer_id(leader_id)?;
|
||||
let mut items_to_activate = Vec::new();
|
||||
|
||||
let leader_in_this_app;
|
||||
let leader_in_this_project;
|
||||
@@ -3432,36 +3409,79 @@ impl Workspace {
|
||||
}
|
||||
};
|
||||
|
||||
let mut panel_items_to_activate = Vec::new();
|
||||
let mut pane_items_to_activate = Vec::new();
|
||||
for (pane, state) in &self.follower_states {
|
||||
if state.leader_id != leader_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
let transfer_focus = pane.read(cx).has_focus(cx)
|
||||
|| self
|
||||
.docks()
|
||||
.iter()
|
||||
.any(|dock| dock.read(cx).leader_peer_id(cx) == Some(leader_id));
|
||||
|
||||
if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
|
||||
if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
|
||||
if leader_in_this_project || !item.is_project_item(cx) {
|
||||
items_to_activate.push((pane.clone(), item.boxed_clone()));
|
||||
if leader_in_this_project || !item.view.is_project_item(cx) {
|
||||
if let Some(panel_id) = item.location {
|
||||
if *pane == self.active_pane {
|
||||
panel_items_to_activate.push((
|
||||
panel_id,
|
||||
FollowableItemHandle::boxed_clone(item.view.as_ref()),
|
||||
transfer_focus,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
pane_items_to_activate.push((
|
||||
pane.clone(),
|
||||
ItemHandle::boxed_clone(item.view.as_ref()),
|
||||
transfer_focus,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
|
||||
items_to_activate.push((pane.clone(), Box::new(shared_screen)));
|
||||
pane_items_to_activate.push((
|
||||
pane.clone(),
|
||||
Box::new(shared_screen),
|
||||
transfer_focus,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for (pane, item) in items_to_activate {
|
||||
let pane_was_focused = pane.read(cx).has_focus(cx);
|
||||
if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
|
||||
pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
|
||||
} else {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.add_item(item.boxed_clone(), false, false, None, cx)
|
||||
});
|
||||
}
|
||||
for (pane, item, transfer_focus) in pane_items_to_activate {
|
||||
pane.update(cx, |pane, cx| {
|
||||
if let Some(index) = pane.index_for_item(item.as_ref()) {
|
||||
pane.activate_item(index, false, false, cx);
|
||||
} else {
|
||||
pane.add_item(item.boxed_clone(), false, false, None, cx);
|
||||
}
|
||||
|
||||
if pane_was_focused {
|
||||
pane.update(cx, |pane, cx| pane.focus_active_item(cx));
|
||||
if transfer_focus {
|
||||
pane.focus_active_item(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (panel_id, item, transfer_focus) in panel_items_to_activate {
|
||||
for dock in self.docks() {
|
||||
if let Some(panel_index) = dock.read(cx).panel_index_for_remote_id(panel_id) {
|
||||
dock.update(cx, |dock, cx| {
|
||||
dock.activate_panel(panel_index, cx);
|
||||
dock.set_open(true, cx);
|
||||
let panel = dock.panel_for_index(panel_index).unwrap();
|
||||
panel.leader_updated(item, cx);
|
||||
if transfer_focus {
|
||||
panel.focus_handle(cx).focus(cx);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3607,6 +3627,7 @@ impl Workspace {
|
||||
let active_item_id = pane.active_item().map(|item| item.item_id());
|
||||
(
|
||||
pane.items()
|
||||
.iter()
|
||||
.filter_map(|item_handle| {
|
||||
Some(SerializedItem {
|
||||
kind: Arc::from(item_handle.serialized_item_kind()?),
|
||||
@@ -4371,24 +4392,8 @@ impl WorkspaceStore {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let handler_response = workspace.handle_follow(follower.project_id, cx);
|
||||
if response.views.is_empty() {
|
||||
response.views = handler_response.views;
|
||||
} else {
|
||||
response.views.extend_from_slice(&handler_response.views);
|
||||
}
|
||||
|
||||
if let Some(active_view_id) = handler_response.active_view_id.clone() {
|
||||
if response.active_view_id.is_none()
|
||||
|| workspace.project.read(cx).remote_id() == follower.project_id
|
||||
{
|
||||
response.active_view_id = Some(active_view_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(active_view) = handler_response.active_view.clone() {
|
||||
if response.active_view_id.is_none()
|
||||
|| workspace.project.read(cx).remote_id() == follower.project_id
|
||||
{
|
||||
if workspace.project.read(cx).remote_id() == follower.project_id {
|
||||
response.active_view = Some(active_view)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1545,6 +1545,7 @@ mod tests {
|
||||
);
|
||||
let pane_entries = pane
|
||||
.items()
|
||||
.iter()
|
||||
.map(|i| i.project_path(cx).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(pane_entries, &[file1, file2, file3]);
|
||||
@@ -1879,7 +1880,7 @@ mod tests {
|
||||
cx.read(|cx| {
|
||||
let pane = workspace.read(cx).active_pane().read(cx);
|
||||
let mut opened_buffer_paths = pane
|
||||
.items()
|
||||
.items().iter()
|
||||
.map(|i| {
|
||||
i.project_path(cx)
|
||||
.expect("all excluded files that got open should have a path")
|
||||
|
||||
Reference in New Issue
Block a user