Introduce following for assistant panel (#14479)
Release Notes: - Added support for following into the assistant panel. --------- Co-authored-by: Max <max@zed.dev> Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com> Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
parent
977a1b7a82
commit
decdd3b6ac
13 changed files with 819 additions and 541 deletions
|
@ -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, 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
|
||||
|
@ -374,7 +376,7 @@ impl AssistantPanel {
|
|||
|
||||
fn handle_pane_event(
|
||||
&mut self,
|
||||
_pane: View<Pane>,
|
||||
pane: View<Pane>,
|
||||
event: &pane::Event,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
|
@ -384,14 +386,25 @@ impl AssistantPanel {
|
|||
pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
|
||||
|
||||
pane::Event::AddItem { item } => {
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
item.added_to_pane(workspace, self.pane.clone(), cx)
|
||||
});
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pane::Event::RemoveItem { .. } | pane::Event::ActivateItem { .. } => {
|
||||
pane::Event::ActivateItem { local } => {
|
||||
if *local {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.unfollow_in_pane(&pane, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
cx.emit(AssistantPanelEvent::ContextEdited);
|
||||
}
|
||||
|
||||
pane::Event::RemoveItem { .. } => {
|
||||
cx.emit(AssistantPanelEvent::ContextEdited);
|
||||
}
|
||||
|
||||
|
@ -613,12 +626,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),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -722,14 +736,17 @@ impl AssistantPanel {
|
|||
&mut self,
|
||||
id: ContextId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
) -> Task<Result<View<ContextEditor>>> {
|
||||
let existing_context = self.pane.read(cx).items().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)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -755,10 +772,9 @@ impl AssistantPanel {
|
|||
let editor = cx.new_view(|cx| {
|
||||
ContextEditor::for_context(context, fs, workspace, lsp_adapter_delegate, cx)
|
||||
});
|
||||
this.show_context(editor, cx);
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
Ok(())
|
||||
this.show_context(editor.clone(), cx);
|
||||
anyhow::Ok(editor)
|
||||
})?
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -878,6 +894,14 @@ impl Panel for AssistantPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn pane(&self) -> Option<View<Pane>> {
|
||||
Some(self.pane.clone())
|
||||
}
|
||||
|
||||
fn remote_id() -> Option<proto::PanelId> {
|
||||
Some(proto::PanelId::AssistantPanel)
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
if !settings.enabled || !settings.button {
|
||||
|
@ -924,6 +948,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>,
|
||||
|
@ -971,6 +996,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 +1239,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 +1498,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 +1961,7 @@ impl ContextEditor {
|
|||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ContextEditorEvent> for ContextEditor {}
|
||||
impl EventEmitter<EditorEvent> for ContextEditor {}
|
||||
impl EventEmitter<SearchEvent> for ContextEditor {}
|
||||
|
||||
impl Render for ContextEditor {
|
||||
|
@ -1977,13 +2003,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 +2019,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 +2044,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 +2156,127 @@ 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 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 +2513,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();
|
||||
|
@ -194,7 +194,7 @@ async fn test_basic_following(
|
|||
|
||||
// Client C unfollows client A.
|
||||
workspace_c.update(cx_c, |workspace, cx| {
|
||||
workspace.unfollow(&workspace.active_pane().clone(), cx);
|
||||
workspace.unfollow(peer_id_a, cx).unwrap();
|
||||
});
|
||||
|
||||
// All clients see that clients B is following client A.
|
||||
|
@ -398,7 +398,7 @@ async fn test_basic_following(
|
|||
|
||||
// After unfollowing, client B stops receiving updates from client A.
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.unfollow(&workspace.active_pane().clone(), cx)
|
||||
workspace.unfollow(peer_id_a, cx).unwrap()
|
||||
});
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
workspace.activate_item(&editor_a2, cx)
|
||||
|
|
|
@ -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?;
|
||||
|
@ -563,6 +572,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>);
|
||||
|
|
|
@ -271,7 +271,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>| {
|
||||
|
|
|
@ -8812,7 +8812,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(),
|
||||
|
@ -8904,7 +8903,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,
|
||||
|
@ -327,6 +308,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(
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1629,7 +1629,7 @@ message Follow {
|
|||
|
||||
message FollowResponse {
|
||||
View active_view = 3;
|
||||
// TODO: after 0.124.0 is retired, remove these.
|
||||
// TODO: Remove after version 0.145.x stabilizes.
|
||||
optional ViewId active_view_id = 1;
|
||||
repeated View views = 2;
|
||||
}
|
||||
|
@ -1640,7 +1640,7 @@ message UpdateFollowers {
|
|||
reserved 3;
|
||||
oneof variant {
|
||||
View create_view = 5;
|
||||
// TODO: after 0.124.0 is retired, remove these.
|
||||
// TODO: Remove after version 0.145.x stabilizes.
|
||||
UpdateActiveView update_active_view = 4;
|
||||
UpdateView update_view = 6;
|
||||
}
|
||||
|
@ -1673,6 +1673,10 @@ message UpdateActiveView {
|
|||
View view = 3;
|
||||
}
|
||||
|
||||
enum PanelId {
|
||||
AssistantPanel = 0;
|
||||
}
|
||||
|
||||
message UpdateView {
|
||||
ViewId id = 1;
|
||||
optional PeerId leader_id = 2;
|
||||
|
@ -1695,10 +1699,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 {
|
||||
|
@ -1716,6 +1722,11 @@ message View {
|
|||
uint64 channel_id = 1;
|
||||
Editor editor = 2;
|
||||
}
|
||||
|
||||
message ContextEditor {
|
||||
string context_id = 1;
|
||||
Editor editor = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Collaborator {
|
||||
|
|
|
@ -168,7 +168,11 @@ impl TitleBar {
|
|||
cx.listener(move |this, _, cx| {
|
||||
this.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.follow(peer_id, cx);
|
||||
if is_following {
|
||||
workspace.unfollow(peer_id, cx);
|
||||
} else {
|
||||
workspace.follow(peer_id, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::persistence::model::DockData;
|
||||
use crate::{status_bar::StatusItemView, Workspace};
|
||||
use crate::{DraggedDock, Event};
|
||||
use crate::{DraggedDock, Event, Pane};
|
||||
use client::proto;
|
||||
use gpui::{
|
||||
deferred, div, px, Action, AnchorCorner, AnyView, AppContext, Axis, Entity, EntityId,
|
||||
EventEmitter, FocusHandle, FocusableView, IntoElement, KeyContext, MouseButton, MouseDownEvent,
|
||||
|
@ -23,6 +24,8 @@ pub enum PanelEvent {
|
|||
Close,
|
||||
}
|
||||
|
||||
pub use proto::PanelId;
|
||||
|
||||
pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
|
||||
fn persistent_name() -> &'static str;
|
||||
fn position(&self, cx: &WindowContext) -> DockPosition;
|
||||
|
@ -44,6 +47,12 @@ 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 pane(&self) -> Option<View<Pane>> {
|
||||
None
|
||||
}
|
||||
fn remote_id() -> Option<proto::PanelId> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PanelHandle: Send + Sync {
|
||||
|
@ -55,6 +64,8 @@ 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 remote_id(&self) -> Option<proto::PanelId>;
|
||||
fn pane(&self, cx: &WindowContext) -> Option<View<Pane>>;
|
||||
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 +112,14 @@ where
|
|||
self.update(cx, |this, cx| this.set_active(active, cx))
|
||||
}
|
||||
|
||||
fn pane(&self, cx: &WindowContext) -> Option<View<Pane>> {
|
||||
self.read(cx).pane()
|
||||
}
|
||||
|
||||
fn remote_id(&self) -> Option<PanelId> {
|
||||
T::remote_id()
|
||||
}
|
||||
|
||||
fn size(&self, cx: &WindowContext) -> Pixels {
|
||||
self.read(cx).size(cx)
|
||||
}
|
||||
|
@ -296,6 +315,12 @@ impl Dock {
|
|||
.position(|entry| entry.panel.persistent_name() == ui_name)
|
||||
}
|
||||
|
||||
pub fn panel_index_for_proto_id(&self, panel_id: PanelId) -> Option<usize> {
|
||||
self.panel_entries
|
||||
.iter()
|
||||
.position(|entry| entry.panel.remote_id() == Some(panel_id))
|
||||
}
|
||||
|
||||
pub fn active_panel_index(&self) -> usize {
|
||||
self.active_panel_index
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
@ -548,11 +532,11 @@ impl<T: Item> ItemHandle for View<T> {
|
|||
|
||||
if let Some(item) = item.to_followable_item_handle(cx) {
|
||||
let leader_id = workspace.leader_for_pane(&pane);
|
||||
let follow_event = item.to_follow_event(event);
|
||||
if leader_id.is_some()
|
||||
&& matches!(follow_event, Some(FollowEvent::Unfollow))
|
||||
{
|
||||
workspace.unfollow(&pane, cx);
|
||||
|
||||
if let Some(leader_id) = leader_id {
|
||||
if let Some(FollowEvent::Unfollow) = item.to_follow_event(event) {
|
||||
workspace.unfollow(leader_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
if item.focus_handle(cx).contains_focused(cx) {
|
||||
|
@ -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,6 +780,7 @@ 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 dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup>;
|
||||
}
|
||||
|
||||
pub trait FollowableItemHandle: ItemHandle {
|
||||
|
@ -815,6 +802,7 @@ 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> {
|
||||
|
@ -868,6 +856,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,6 +1,7 @@
|
|||
use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
|
||||
use anyhow::{anyhow, Result};
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use client::proto::PeerId;
|
||||
use collections::HashMap;
|
||||
use gpui::{
|
||||
point, size, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels,
|
||||
|
@ -95,7 +96,7 @@ impl PaneGroup {
|
|||
pub(crate) fn render(
|
||||
&self,
|
||||
project: &Model<Project>,
|
||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
||||
follower_states: &HashMap<PeerId, FollowerState>,
|
||||
active_call: Option<&Model<ActiveCall>>,
|
||||
active_pane: &View<Pane>,
|
||||
zoomed: Option<&AnyWeakView>,
|
||||
|
@ -168,7 +169,7 @@ impl Member {
|
|||
&self,
|
||||
project: &Model<Project>,
|
||||
basis: usize,
|
||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
||||
follower_states: &HashMap<PeerId, FollowerState>,
|
||||
active_call: Option<&Model<ActiveCall>>,
|
||||
active_pane: &View<Pane>,
|
||||
zoomed: Option<&AnyWeakView>,
|
||||
|
@ -181,19 +182,29 @@ impl Member {
|
|||
return div().into_any();
|
||||
}
|
||||
|
||||
let follower_state = follower_states.get(pane);
|
||||
|
||||
let leader = follower_state.and_then(|state| {
|
||||
let room = active_call?.read(cx).room()?.read(cx);
|
||||
room.remote_participant_for_peer_id(state.leader_id)
|
||||
let follower_state = follower_states.iter().find_map(|(leader_id, state)| {
|
||||
if state.center_pane == *pane {
|
||||
Some((*leader_id, state))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let is_in_unshared_view = follower_state.map_or(false, |state| {
|
||||
let leader = follower_state.as_ref().and_then(|(leader_id, _)| {
|
||||
let room = active_call?.read(cx).room()?.read(cx);
|
||||
room.remote_participant_for_peer_id(*leader_id)
|
||||
});
|
||||
|
||||
let is_in_unshared_view = follower_state.as_ref().map_or(false, |(_, state)| {
|
||||
state.active_view_id.is_some_and(|view_id| {
|
||||
!state.items_by_leader_view_id.contains_key(&view_id)
|
||||
})
|
||||
});
|
||||
|
||||
let is_in_panel = follower_state
|
||||
.as_ref()
|
||||
.map_or(false, |(_, state)| state.dock_pane.is_some());
|
||||
|
||||
let mut leader_border = None;
|
||||
let mut leader_status_box = None;
|
||||
let mut leader_join_data = None;
|
||||
|
@ -203,7 +214,11 @@ impl Member {
|
|||
.players()
|
||||
.color_for_participant(leader.participant_index.0)
|
||||
.cursor;
|
||||
leader_color.fade_out(0.3);
|
||||
if is_in_panel {
|
||||
leader_color.fade_out(0.75);
|
||||
} else {
|
||||
leader_color.fade_out(0.3);
|
||||
}
|
||||
leader_border = Some(leader_color);
|
||||
|
||||
leader_status_box = match leader.location {
|
||||
|
@ -483,7 +498,7 @@ impl PaneAxis {
|
|||
&self,
|
||||
project: &Model<Project>,
|
||||
basis: usize,
|
||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
||||
follower_states: &HashMap<PeerId, FollowerState>,
|
||||
active_call: Option<&Model<ActiveCall>>,
|
||||
active_pane: &View<Pane>,
|
||||
zoomed: Option<&AnyWeakView>,
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue