Merge main
Co-Authored-By: Marshall <marshall@zed.dev>
This commit is contained in:
commit
65bb05af4c
146 changed files with 18353 additions and 9324 deletions
|
@ -20,6 +20,7 @@ test-support = [
|
|||
|
||||
[dependencies]
|
||||
db = { path = "../db2", package = "db2" }
|
||||
call = { path = "../call2", package = "call2" }
|
||||
client = { path = "../client2", package = "client2" }
|
||||
collections = { path = "../collections" }
|
||||
# context_menu = { path = "../context_menu" }
|
||||
|
@ -36,7 +37,6 @@ theme = { path = "../theme2", package = "theme2" }
|
|||
util = { path = "../util" }
|
||||
ui = { package = "ui2", path = "../ui2" }
|
||||
|
||||
async-trait.workspace = true
|
||||
async-recursion = "1.0.0"
|
||||
itertools = "0.10"
|
||||
bincode = "1.2.1"
|
||||
|
|
|
@ -78,7 +78,7 @@ impl Settings for ItemSettings {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Debug)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
|
||||
pub enum ItemEvent {
|
||||
CloseItem,
|
||||
UpdateTab,
|
||||
|
@ -92,7 +92,9 @@ pub struct BreadcrumbText {
|
|||
pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
|
||||
}
|
||||
|
||||
pub trait Item: FocusableView + EventEmitter<ItemEvent> {
|
||||
pub trait Item: FocusableView + EventEmitter<Self::Event> {
|
||||
type Event;
|
||||
|
||||
fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
||||
fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
|
||||
fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
|
||||
|
@ -155,6 +157,8 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
|
|||
unimplemented!("reload() must be implemented if can_save() returns true")
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, f: impl FnMut(ItemEvent));
|
||||
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
|
@ -206,12 +210,12 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
|
|||
}
|
||||
|
||||
pub trait ItemHandle: 'static + Send {
|
||||
fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
|
||||
fn subscribe_to_item_events(
|
||||
&self,
|
||||
cx: &mut WindowContext,
|
||||
handler: Box<dyn Fn(&ItemEvent, &mut WindowContext) + Send>,
|
||||
handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
|
||||
) -> gpui::Subscription;
|
||||
fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
|
||||
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
|
||||
fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
|
||||
fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
|
||||
|
@ -285,20 +289,20 @@ impl dyn ItemHandle {
|
|||
}
|
||||
|
||||
impl<T: Item> ItemHandle for View<T> {
|
||||
fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
|
||||
self.focus_handle(cx)
|
||||
}
|
||||
|
||||
fn subscribe_to_item_events(
|
||||
&self,
|
||||
cx: &mut WindowContext,
|
||||
handler: Box<dyn Fn(&ItemEvent, &mut WindowContext) + Send>,
|
||||
handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
|
||||
) -> gpui::Subscription {
|
||||
cx.subscribe(self, move |_, event, cx| {
|
||||
handler(event, cx);
|
||||
T::to_item_events(event, |item_event| handler(item_event, cx));
|
||||
})
|
||||
}
|
||||
|
||||
fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
|
||||
self.focus_handle(cx)
|
||||
}
|
||||
|
||||
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
|
||||
self.read(cx).tab_tooltip_text(cx)
|
||||
}
|
||||
|
@ -461,7 +465,7 @@ impl<T: Item> ItemHandle for View<T> {
|
|||
}
|
||||
}
|
||||
|
||||
match event {
|
||||
T::to_item_events(event, |event| match event {
|
||||
ItemEvent::CloseItem => {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_item_by_id(item.item_id(), crate::SaveIntent::Close, cx)
|
||||
|
@ -489,7 +493,7 @@ impl<T: Item> ItemHandle for View<T> {
|
|||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
|
||||
|
@ -655,12 +659,7 @@ pub enum FollowEvent {
|
|||
Unfollow,
|
||||
}
|
||||
|
||||
pub trait FollowableEvents {
|
||||
fn to_follow_event(&self) -> Option<FollowEvent>;
|
||||
}
|
||||
|
||||
pub trait FollowableItem: Item {
|
||||
type FollowableEvent: FollowableEvents;
|
||||
fn remote_id(&self) -> Option<ViewId>;
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
|
||||
fn from_state_proto(
|
||||
|
@ -670,9 +669,10 @@ pub trait FollowableItem: Item {
|
|||
state: &mut Option<proto::view::Variant>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Task<Result<View<Self>>>>;
|
||||
fn to_follow_event(event: &Self::Event) -> Option<FollowEvent>;
|
||||
fn add_event_to_update_proto(
|
||||
&self,
|
||||
event: &Self::FollowableEvent,
|
||||
event: &Self::Event,
|
||||
update: &mut Option<proto::update_view::Variant>,
|
||||
cx: &WindowContext,
|
||||
) -> bool;
|
||||
|
@ -683,7 +683,6 @@ pub trait FollowableItem: Item {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) -> 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>);
|
||||
}
|
||||
|
||||
|
@ -739,10 +738,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
|
|||
}
|
||||
|
||||
fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
|
||||
event
|
||||
.downcast_ref()
|
||||
.map(T::FollowableEvent::to_follow_event)
|
||||
.flatten()
|
||||
T::to_follow_event(event.downcast_ref()?)
|
||||
}
|
||||
|
||||
fn apply_update_proto(
|
||||
|
@ -929,6 +925,12 @@ pub mod test {
|
|||
}
|
||||
|
||||
impl Item for TestItem {
|
||||
type Event = ItemEvent;
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
|
||||
f(*event)
|
||||
}
|
||||
|
||||
fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
|
||||
self.tab_descriptions.as_ref().and_then(|descriptions| {
|
||||
let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
|
||||
|
|
|
@ -135,24 +135,22 @@ impl Workspace {
|
|||
}
|
||||
|
||||
pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
|
||||
todo!()
|
||||
// self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
|
||||
// self.show_notification(toast.id, cx, |cx| {
|
||||
// cx.add_view(|_cx| match toast.on_click.as_ref() {
|
||||
// Some((click_msg, on_click)) => {
|
||||
// let on_click = on_click.clone();
|
||||
// simple_message_notification::MessageNotification::new(toast.msg.clone())
|
||||
// .with_click_message(click_msg.clone())
|
||||
// .on_click(move |cx| on_click(cx))
|
||||
// }
|
||||
// None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
|
||||
// })
|
||||
// })
|
||||
self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
|
||||
self.show_notification(toast.id, cx, |cx| {
|
||||
cx.build_view(|_cx| match toast.on_click.as_ref() {
|
||||
Some((click_msg, on_click)) => {
|
||||
let on_click = on_click.clone();
|
||||
simple_message_notification::MessageNotification::new(toast.msg.clone())
|
||||
.with_click_message(click_msg.clone())
|
||||
.on_click(move |cx| on_click(cx))
|
||||
}
|
||||
None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext<Self>) {
|
||||
todo!()
|
||||
// self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
|
||||
self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
|
||||
}
|
||||
|
||||
fn dismiss_notification_internal(
|
||||
|
@ -179,33 +177,10 @@ pub mod simple_message_notification {
|
|||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle,
|
||||
ViewContext,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
|
||||
|
||||
#[derive(Clone, Default, Deserialize, PartialEq)]
|
||||
pub struct OsOpen(pub Cow<'static, str>);
|
||||
|
||||
impl OsOpen {
|
||||
pub fn new<I: Into<Cow<'static, str>>>(url: I) -> Self {
|
||||
OsOpen(url.into())
|
||||
}
|
||||
}
|
||||
|
||||
// todo!()
|
||||
// impl_actions!(message_notifications, [OsOpen]);
|
||||
//
|
||||
// todo!()
|
||||
// pub fn init(cx: &mut AppContext) {
|
||||
// cx.add_action(MessageNotification::dismiss);
|
||||
// cx.add_action(
|
||||
// |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext<Workspace>| {
|
||||
// cx.platform().open_url(open_action.0.as_ref());
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
|
||||
enum NotificationMessage {
|
||||
Text(SharedString),
|
||||
Element(fn(TextStyle, &AppContext) -> AnyElement),
|
||||
|
@ -213,7 +188,7 @@ pub mod simple_message_notification {
|
|||
|
||||
pub struct MessageNotification {
|
||||
message: NotificationMessage,
|
||||
on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>) + Send + Sync>>,
|
||||
on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
|
||||
click_message: Option<SharedString>,
|
||||
}
|
||||
|
||||
|
@ -252,7 +227,7 @@ pub mod simple_message_notification {
|
|||
|
||||
pub fn on_click<F>(mut self, on_click: F) -> Self
|
||||
where
|
||||
F: 'static + Send + Sync + Fn(&mut ViewContext<Self>),
|
||||
F: 'static + Fn(&mut ViewContext<Self>),
|
||||
{
|
||||
self.on_click = Some(Arc::new(on_click));
|
||||
self
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
|
||||
item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle},
|
||||
toolbar::Toolbar,
|
||||
workspace_settings::{AutosaveSetting, WorkspaceSettings},
|
||||
NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
|
||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||
use anyhow::Result;
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
use gpui::{
|
||||
actions, overlay, prelude::*, Action, AnchorCorner, AnyWeakView, AppContext,
|
||||
actions, overlay, prelude::*, rems, Action, AnchorCorner, AnyWeakView, AppContext,
|
||||
AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable,
|
||||
FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View, ViewContext,
|
||||
VisualContext, WeakView, WindowContext,
|
||||
|
@ -26,7 +26,10 @@ use std::{
|
|||
},
|
||||
};
|
||||
|
||||
use ui::{prelude::*, right_click_menu, Color, Icon, IconButton, IconElement, Tooltip};
|
||||
use ui::{
|
||||
h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize,
|
||||
Indicator, Label, Tooltip,
|
||||
};
|
||||
use ui::{v_stack, ContextMenu};
|
||||
use util::truncate_and_remove_front;
|
||||
|
||||
|
@ -535,18 +538,21 @@ impl Pane {
|
|||
|
||||
pub(crate) fn open_item(
|
||||
&mut self,
|
||||
project_entry_id: ProjectEntryId,
|
||||
project_entry_id: Option<ProjectEntryId>,
|
||||
focus_item: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
|
||||
) -> Box<dyn ItemHandle> {
|
||||
let mut existing_item = None;
|
||||
for (index, item) in self.items.iter().enumerate() {
|
||||
if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id]
|
||||
{
|
||||
let item = item.boxed_clone();
|
||||
existing_item = Some((index, item));
|
||||
break;
|
||||
if let Some(project_entry_id) = project_entry_id {
|
||||
for (index, item) in self.items.iter().enumerate() {
|
||||
if item.is_singleton(cx)
|
||||
&& item.project_entry_ids(cx).as_slice() == [project_entry_id]
|
||||
{
|
||||
let item = item.boxed_clone();
|
||||
existing_item = Some((index, item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1413,22 +1419,7 @@ impl Pane {
|
|||
cx: &mut ViewContext<'_, Pane>,
|
||||
) -> impl IntoElement {
|
||||
let label = item.tab_content(Some(detail), cx);
|
||||
let close_icon = || {
|
||||
let id = item.item_id();
|
||||
|
||||
div()
|
||||
.id(ix)
|
||||
.invisible()
|
||||
.group_hover("", |style| style.visible())
|
||||
.child(
|
||||
IconButton::new("close_tab", Icon::Close).on_click(cx.listener(
|
||||
move |pane, _, cx| {
|
||||
pane.close_item_by_id(id, SaveIntent::Close, cx)
|
||||
.detach_and_log_err(cx);
|
||||
},
|
||||
)),
|
||||
)
|
||||
};
|
||||
let close_side = &ItemSettings::get_global(cx).close_position;
|
||||
|
||||
let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
|
||||
false => (
|
||||
|
@ -1445,251 +1436,260 @@ impl Pane {
|
|||
),
|
||||
};
|
||||
|
||||
let close_right = ItemSettings::get_global(cx).close_position.right();
|
||||
let is_active = ix == self.active_item_index;
|
||||
|
||||
let indicator = {
|
||||
let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
|
||||
(true, _) => Some(Color::Warning),
|
||||
(_, true) => Some(Color::Accent),
|
||||
(false, false) => None,
|
||||
};
|
||||
|
||||
h_stack()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.justify_center()
|
||||
.absolute()
|
||||
.map(|this| match close_side {
|
||||
ClosePosition::Left => this.right_1(),
|
||||
ClosePosition::Right => this.left_1(),
|
||||
})
|
||||
.when_some(indicator_color, |this, indicator_color| {
|
||||
this.child(Indicator::dot().color(indicator_color))
|
||||
})
|
||||
};
|
||||
|
||||
let close_button = {
|
||||
let id = item.item_id();
|
||||
|
||||
h_stack()
|
||||
.invisible()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.justify_center()
|
||||
.absolute()
|
||||
.map(|this| match close_side {
|
||||
ClosePosition::Left => this.left_1(),
|
||||
ClosePosition::Right => this.right_1(),
|
||||
})
|
||||
.group_hover("", |style| style.visible())
|
||||
.child(
|
||||
// TODO: Fix button size
|
||||
IconButton::new("close tab", Icon::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::None)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(move |pane, _, cx| {
|
||||
pane.close_item_by_id(id, SaveIntent::Close, cx)
|
||||
.detach_and_log_err(cx);
|
||||
})),
|
||||
)
|
||||
};
|
||||
|
||||
let tab = div()
|
||||
.group("")
|
||||
.id(ix)
|
||||
.cursor_pointer()
|
||||
.when_some(item.tab_tooltip_text(cx), |div, text| {
|
||||
div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
|
||||
})
|
||||
.on_click(cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)))
|
||||
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
|
||||
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
|
||||
// .on_drop(|_view, state: View<DraggedTab>, cx| {
|
||||
// eprintln!("{:?}", state.read(cx));
|
||||
// })
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
// todo!("Nate - I need to do some work to balance all the items in the tab once things stablize")
|
||||
.map(|this| {
|
||||
if close_right {
|
||||
this.pl_3().pr_1()
|
||||
} else {
|
||||
this.pr_1().pr_3()
|
||||
}
|
||||
})
|
||||
.py_1()
|
||||
.bg(tab_bg)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.text_color(if is_active {
|
||||
cx.theme().colors().text
|
||||
} else {
|
||||
cx.theme().colors().text_muted
|
||||
})
|
||||
.bg(tab_bg)
|
||||
// 30px @ 16px/rem
|
||||
.h(rems(1.875))
|
||||
.map(|this| {
|
||||
let is_first_item = ix == 0;
|
||||
let is_last_item = ix == self.items.len() - 1;
|
||||
match ix.cmp(&self.active_item_index) {
|
||||
cmp::Ordering::Less => this.border_l().mr_px(),
|
||||
cmp::Ordering::Greater => {
|
||||
if is_last_item {
|
||||
this.mr_px().ml_px()
|
||||
cmp::Ordering::Less => {
|
||||
if is_first_item {
|
||||
this.pl_px().pr_px().border_b()
|
||||
} else {
|
||||
this.border_r().ml_px()
|
||||
this.border_l().pr_px().border_b()
|
||||
}
|
||||
}
|
||||
cmp::Ordering::Greater => {
|
||||
if is_last_item {
|
||||
this.pr_px().pl_px().border_b()
|
||||
} else {
|
||||
this.border_r().pl_px().border_b()
|
||||
}
|
||||
}
|
||||
cmp::Ordering::Equal => {
|
||||
if is_first_item {
|
||||
this.pl_px().border_r().pb_px()
|
||||
} else {
|
||||
this.border_l().border_r().pb_px()
|
||||
}
|
||||
}
|
||||
cmp::Ordering::Equal => this.border_l().border_r(),
|
||||
}
|
||||
})
|
||||
// .hover(|h| h.bg(tab_hover_bg))
|
||||
// .active(|a| a.bg(tab_active_bg))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
h_stack()
|
||||
.group("")
|
||||
.id(ix)
|
||||
.relative()
|
||||
.h_full()
|
||||
.cursor_pointer()
|
||||
.when_some(item.tab_tooltip_text(cx), |div, text| {
|
||||
div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)),
|
||||
)
|
||||
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
|
||||
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
|
||||
// .on_drop(|_view, state: View<DraggedTab>, cx| {
|
||||
// eprintln!("{:?}", state.read(cx));
|
||||
// })
|
||||
.px_5()
|
||||
// .hover(|h| h.bg(tab_hover_bg))
|
||||
// .active(|a| a.bg(tab_active_bg))
|
||||
.gap_1()
|
||||
.text_color(text_color)
|
||||
.children(
|
||||
item.has_conflict(cx)
|
||||
.then(|| {
|
||||
div().border().border_color(gpui::red()).child(
|
||||
IconElement::new(Icon::ExclamationTriangle)
|
||||
.size(ui::IconSize::Small)
|
||||
.color(Color::Warning),
|
||||
)
|
||||
})
|
||||
.or(item.is_dirty(cx).then(|| {
|
||||
div().border().border_color(gpui::red()).child(
|
||||
IconElement::new(Icon::ExclamationTriangle)
|
||||
.size(ui::IconSize::Small)
|
||||
.color(Color::Info),
|
||||
)
|
||||
})),
|
||||
)
|
||||
.children((!close_right).then(|| close_icon()))
|
||||
.child(label)
|
||||
.children(close_right.then(|| close_icon())),
|
||||
.child(indicator)
|
||||
.child(close_button)
|
||||
.child(label),
|
||||
);
|
||||
|
||||
right_click_menu(ix).trigger(tab).menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, cx| {
|
||||
menu.action(
|
||||
"Close Active Item",
|
||||
CloseActiveItem { save_intent: None }.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
.action("Close Inactive Items", CloseInactiveItems.boxed_clone(), cx)
|
||||
.action("Close Clean Items", CloseCleanItems.boxed_clone(), cx)
|
||||
.action(
|
||||
"Close Items To The Left",
|
||||
CloseItemsToTheLeft.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
.action(
|
||||
"Close Items To The Right",
|
||||
CloseItemsToTheRight.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
.action(
|
||||
"Close All Items",
|
||||
CloseAllItems { save_intent: None }.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
menu.action("Close", CloseActiveItem { save_intent: None }.boxed_clone())
|
||||
.action("Close Others", CloseInactiveItems.boxed_clone())
|
||||
.separator()
|
||||
.action("Close Left", CloseItemsToTheLeft.boxed_clone())
|
||||
.action("Close Right", CloseItemsToTheRight.boxed_clone())
|
||||
.separator()
|
||||
.action("Close Clean", CloseCleanItems.boxed_clone())
|
||||
.action(
|
||||
"Close All",
|
||||
CloseAllItems { save_intent: None }.boxed_clone(),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement {
|
||||
div()
|
||||
.group("tab_bar")
|
||||
.id("tab_bar")
|
||||
.group("tab_bar")
|
||||
.track_focus(&self.tab_bar_focus_handle)
|
||||
.w_full()
|
||||
// 30px @ 16px/rem
|
||||
.h(rems(1.875))
|
||||
.overflow_hidden()
|
||||
.flex()
|
||||
.flex_none()
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
// Left Side
|
||||
.child(
|
||||
div()
|
||||
.relative()
|
||||
.px_1()
|
||||
h_stack()
|
||||
.flex()
|
||||
.flex_none()
|
||||
.gap_2()
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.border_b()
|
||||
.border_r()
|
||||
.border_color(cx.theme().colors().border)
|
||||
// Nav Buttons
|
||||
.child(
|
||||
div()
|
||||
.right_0()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(
|
||||
div().border().border_color(gpui::red()).child(
|
||||
IconButton::new("navigate_backward", Icon::ArrowLeft)
|
||||
.on_click({
|
||||
let view = cx.view().clone();
|
||||
move |_, cx| view.update(cx, Self::navigate_backward)
|
||||
})
|
||||
.disabled(!self.can_navigate_backward()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().border().border_color(gpui::red()).child(
|
||||
IconButton::new("navigate_forward", Icon::ArrowRight)
|
||||
.on_click({
|
||||
let view = cx.view().clone();
|
||||
move |_, cx| view.update(cx, Self::navigate_backward)
|
||||
})
|
||||
.disabled(!self.can_navigate_forward()),
|
||||
),
|
||||
),
|
||||
IconButton::new("navigate_backward", Icon::ArrowLeft)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let view = cx.view().clone();
|
||||
move |_, cx| view.update(cx, Self::navigate_backward)
|
||||
})
|
||||
.disabled(!self.can_navigate_backward()),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("navigate_forward", Icon::ArrowRight)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let view = cx.view().clone();
|
||||
move |_, cx| view.update(cx, Self::navigate_backward)
|
||||
})
|
||||
.disabled(!self.can_navigate_forward()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().flex_1().h_full().child(
|
||||
div().id("tabs").flex().overflow_x_scroll().children(
|
||||
self.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(self.tab_details(cx))
|
||||
.map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
|
||||
div()
|
||||
.relative()
|
||||
.flex_1()
|
||||
.h_full()
|
||||
.overflow_hidden_x()
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.z_index(1)
|
||||
.size_full()
|
||||
.border_b()
|
||||
.border_color(cx.theme().colors().border),
|
||||
)
|
||||
.child(
|
||||
h_stack().id("tabs").z_index(2).children(
|
||||
self.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(self.tab_details(cx))
|
||||
.map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
// Right Side
|
||||
.child(
|
||||
div()
|
||||
.px_1()
|
||||
h_stack()
|
||||
.flex()
|
||||
.flex_none()
|
||||
.gap_2()
|
||||
// Nav Buttons
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.border_b()
|
||||
.border_l()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(
|
||||
div()
|
||||
.bg(gpui::blue())
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(IconButton::new("plus", Icon::Plus).on_click(
|
||||
cx.listener(|this, _, cx| {
|
||||
let menu = ContextMenu::build(cx, |menu, cx| {
|
||||
menu.action("New File", NewFile.boxed_clone(), cx)
|
||||
.action(
|
||||
"New Terminal",
|
||||
NewCenterTerminal.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
.action(
|
||||
"New Search",
|
||||
NewSearch.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(
|
||||
&menu,
|
||||
|this, _, event: &DismissEvent, cx| {
|
||||
this.focus(cx);
|
||||
this.new_item_menu = None;
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
this.new_item_menu = Some(menu);
|
||||
}),
|
||||
))
|
||||
.when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
|
||||
el.child(Self::render_menu_overlay(new_item_menu))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(IconButton::new("split", Icon::Split).on_click(
|
||||
cx.listener(|this, _, cx| {
|
||||
let menu = ContextMenu::build(cx, |menu, cx| {
|
||||
menu.action(
|
||||
"Split Right",
|
||||
SplitRight.boxed_clone(),
|
||||
cx,
|
||||
IconButton::new("plus", Icon::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
let menu = ContextMenu::build(cx, |menu, cx| {
|
||||
menu.action("New File", NewFile.boxed_clone())
|
||||
.action(
|
||||
"New Terminal",
|
||||
NewCenterTerminal.boxed_clone(),
|
||||
)
|
||||
.action("Split Left", SplitLeft.boxed_clone(), cx)
|
||||
.action("Split Up", SplitUp.boxed_clone(), cx)
|
||||
.action("Split Down", SplitDown.boxed_clone(), cx)
|
||||
});
|
||||
cx.subscribe(
|
||||
&menu,
|
||||
|this, _, event: &DismissEvent, cx| {
|
||||
this.focus(cx);
|
||||
this.split_item_menu = None;
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
this.split_item_menu = Some(menu);
|
||||
}),
|
||||
))
|
||||
.when_some(
|
||||
self.split_item_menu.as_ref(),
|
||||
|el, split_item_menu| {
|
||||
el.child(Self::render_menu_overlay(split_item_menu))
|
||||
},
|
||||
),
|
||||
),
|
||||
.action("New Search", NewSearch.boxed_clone())
|
||||
});
|
||||
cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
|
||||
this.focus(cx);
|
||||
this.new_item_menu = None;
|
||||
})
|
||||
.detach();
|
||||
this.new_item_menu = Some(menu);
|
||||
})),
|
||||
)
|
||||
.when_some(self.new_item_menu.as_ref(), |el, new_item_menu| {
|
||||
el.child(Self::render_menu_overlay(new_item_menu))
|
||||
})
|
||||
.child(
|
||||
IconButton::new("split", Icon::Split)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
let menu = ContextMenu::build(cx, |menu, cx| {
|
||||
menu.action("Split Right", SplitRight.boxed_clone())
|
||||
.action("Split Left", SplitLeft.boxed_clone())
|
||||
.action("Split Up", SplitUp.boxed_clone())
|
||||
.action("Split Down", SplitDown.boxed_clone())
|
||||
});
|
||||
cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| {
|
||||
this.focus(cx);
|
||||
this.split_item_menu = None;
|
||||
})
|
||||
.detach();
|
||||
this.split_item_menu = Some(menu);
|
||||
})),
|
||||
)
|
||||
.when_some(self.split_item_menu.as_ref(), |el, split_item_menu| {
|
||||
el.child(Self::render_menu_overlay(split_item_menu))
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -2107,6 +2107,8 @@ impl Render for Pane {
|
|||
v_stack()
|
||||
.key_context("Pane")
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
.on_focus_in({
|
||||
let this = this.clone();
|
||||
move |event, cx| {
|
||||
|
@ -2174,7 +2176,6 @@ impl Render for Pane {
|
|||
pane.close_all_items(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
}))
|
||||
.size_full()
|
||||
.on_action(
|
||||
cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
|
||||
pane.close_active_item(action, cx)
|
||||
|
@ -2186,8 +2187,11 @@ impl Render for Pane {
|
|||
.child(if let Some(item) = self.active_item() {
|
||||
div().flex().flex_1().child(item.to_any())
|
||||
} else {
|
||||
// todo!()
|
||||
div().child("Empty Pane")
|
||||
h_stack()
|
||||
.items_center()
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.child(Label::new("Open a file or project to get started.").color(Color::Muted))
|
||||
})
|
||||
|
||||
// enum MouseNavigationHandler {}
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
use crate::{AppState, FollowerState, Pane, Workspace};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use collections::HashMap;
|
||||
use db::sqlez::{
|
||||
bindable::{Bind, Column, StaticColumnCount},
|
||||
statement::Statement,
|
||||
};
|
||||
use gpui::{
|
||||
point, size, AnyWeakView, Bounds, Div, IntoElement, Model, Pixels, Point, View, ViewContext,
|
||||
point, size, AnyWeakView, Bounds, Div, Entity as _, IntoElement, Model, Pixels, Point, View,
|
||||
ViewContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use project::Project;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, Button};
|
||||
|
||||
const HANDLE_HITBOX_SIZE: f32 = 4.0;
|
||||
const HORIZONTAL_MIN_SIZE: f32 = 80.;
|
||||
|
@ -126,6 +128,7 @@ impl PaneGroup {
|
|||
&self,
|
||||
project: &Model<Project>,
|
||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
||||
active_call: Option<&Model<ActiveCall>>,
|
||||
active_pane: &View<Pane>,
|
||||
zoomed: Option<&AnyWeakView>,
|
||||
app_state: &Arc<AppState>,
|
||||
|
@ -135,6 +138,7 @@ impl PaneGroup {
|
|||
project,
|
||||
0,
|
||||
follower_states,
|
||||
active_call,
|
||||
active_pane,
|
||||
zoomed,
|
||||
app_state,
|
||||
|
@ -196,6 +200,7 @@ impl Member {
|
|||
project: &Model<Project>,
|
||||
basis: usize,
|
||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
||||
active_call: Option<&Model<ActiveCall>>,
|
||||
active_pane: &View<Pane>,
|
||||
zoomed: Option<&AnyWeakView>,
|
||||
app_state: &Arc<AppState>,
|
||||
|
@ -203,19 +208,89 @@ impl Member {
|
|||
) -> impl IntoElement {
|
||||
match self {
|
||||
Member::Pane(pane) => {
|
||||
// todo!()
|
||||
// let pane_element = if Some(pane.into()) == zoomed {
|
||||
// None
|
||||
// } else {
|
||||
// Some(pane)
|
||||
// };
|
||||
let leader = follower_states.get(pane).and_then(|state| {
|
||||
let room = active_call?.read(cx).room()?.read(cx);
|
||||
room.remote_participant_for_peer_id(state.leader_id)
|
||||
});
|
||||
|
||||
div().size_full().child(pane.clone()).into_any()
|
||||
let mut leader_border = None;
|
||||
let mut leader_status_box = None;
|
||||
if let Some(leader) = &leader {
|
||||
let mut leader_color = cx
|
||||
.theme()
|
||||
.players()
|
||||
.color_for_participant(leader.participant_index.0)
|
||||
.cursor;
|
||||
leader_color.fade_out(0.3);
|
||||
leader_border = Some(leader_color);
|
||||
|
||||
// Stack::new()
|
||||
// .with_child(pane_element.contained().with_border(leader_border))
|
||||
// .with_children(leader_status_box)
|
||||
// .into_any()
|
||||
leader_status_box = match leader.location {
|
||||
ParticipantLocation::SharedProject {
|
||||
project_id: leader_project_id,
|
||||
} => {
|
||||
if Some(leader_project_id) == project.read(cx).remote_id() {
|
||||
None
|
||||
} else {
|
||||
let leader_user = leader.user.clone();
|
||||
let leader_user_id = leader.user.id;
|
||||
Some(
|
||||
Button::new(
|
||||
("leader-status", pane.entity_id()),
|
||||
format!(
|
||||
"Follow {} to their active project",
|
||||
leader_user.github_login,
|
||||
),
|
||||
)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, cx| {
|
||||
crate::join_remote_project(
|
||||
leader_project_id,
|
||||
leader_user_id,
|
||||
this.app_state().clone(),
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
},
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
ParticipantLocation::UnsharedProject => Some(Button::new(
|
||||
("leader-status", pane.entity_id()),
|
||||
format!(
|
||||
"{} is viewing an unshared Zed project",
|
||||
leader.user.github_login
|
||||
),
|
||||
)),
|
||||
ParticipantLocation::External => Some(Button::new(
|
||||
("leader-status", pane.entity_id()),
|
||||
format!(
|
||||
"{} is viewing a window outside of Zed",
|
||||
leader.user.github_login
|
||||
),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
div()
|
||||
.relative()
|
||||
.size_full()
|
||||
.child(pane.clone())
|
||||
.when_some(leader_border, |this, color| {
|
||||
this.border_2().border_color(color)
|
||||
})
|
||||
.when_some(leader_status_box, |this, status_box| {
|
||||
this.child(
|
||||
div()
|
||||
.absolute()
|
||||
.w_96()
|
||||
.bottom_3()
|
||||
.right_3()
|
||||
.z_index(1)
|
||||
.child(status_box),
|
||||
)
|
||||
})
|
||||
.into_any()
|
||||
|
||||
// let el = div()
|
||||
// .flex()
|
||||
|
|
122
crates/workspace2/src/shared_screen.rs
Normal file
122
crates/workspace2/src/shared_screen.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use crate::{
|
||||
item::{Item, ItemEvent},
|
||||
ItemNavHistory, WorkspaceId,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use call::participant::{Frame, RemoteVideoTrack};
|
||||
use client::{proto::PeerId, User};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
div, img, AppContext, Div, Element, EventEmitter, FocusHandle, Focusable, FocusableView,
|
||||
InteractiveElement, ParentElement, Render, SharedString, Styled, Task, View, ViewContext,
|
||||
VisualContext, WindowContext,
|
||||
};
|
||||
use std::sync::{Arc, Weak};
|
||||
use ui::{h_stack, Icon, IconElement};
|
||||
|
||||
pub enum Event {
|
||||
Close,
|
||||
}
|
||||
|
||||
pub struct SharedScreen {
|
||||
track: Weak<RemoteVideoTrack>,
|
||||
frame: Option<Frame>,
|
||||
pub peer_id: PeerId,
|
||||
user: Arc<User>,
|
||||
nav_history: Option<ItemNavHistory>,
|
||||
_maintain_frame: Task<Result<()>>,
|
||||
focus: FocusHandle,
|
||||
}
|
||||
|
||||
impl SharedScreen {
|
||||
pub fn new(
|
||||
track: &Arc<RemoteVideoTrack>,
|
||||
peer_id: PeerId,
|
||||
user: Arc<User>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
cx.focus_handle();
|
||||
let mut frames = track.frames();
|
||||
Self {
|
||||
track: Arc::downgrade(track),
|
||||
frame: None,
|
||||
peer_id,
|
||||
user,
|
||||
nav_history: Default::default(),
|
||||
_maintain_frame: cx.spawn(|this, mut cx| async move {
|
||||
while let Some(frame) = frames.next().await {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.frame = Some(frame);
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
|
||||
Ok(())
|
||||
}),
|
||||
focus: cx.focus_handle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<Event> for SharedScreen {}
|
||||
|
||||
impl FocusableView for SharedScreen {
|
||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||
self.focus.clone()
|
||||
}
|
||||
}
|
||||
impl Render for SharedScreen {
|
||||
type Element = Focusable<Div>;
|
||||
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
|
||||
div().track_focus(&self.focus).size_full().children(
|
||||
self.frame
|
||||
.as_ref()
|
||||
.map(|frame| img(frame.image()).size_full()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for SharedScreen {
|
||||
type Event = Event;
|
||||
|
||||
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
|
||||
Some(format!("{}'s screen", self.user.github_login).into())
|
||||
}
|
||||
|
||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(nav_history) = self.nav_history.as_mut() {
|
||||
nav_history.push::<()>(None, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn tab_content(&self, _: Option<usize>, _: &WindowContext<'_>) -> gpui::AnyElement {
|
||||
h_stack()
|
||||
.gap_1()
|
||||
.child(IconElement::new(Icon::Screen))
|
||||
.child(SharedString::from(format!(
|
||||
"{}'s screen",
|
||||
self.user.github_login
|
||||
)))
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
|
||||
self.nav_history = Some(history);
|
||||
}
|
||||
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: WorkspaceId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<View<Self>> {
|
||||
let track = self.track.upgrade()?;
|
||||
Some(cx.build_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx)))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
|
||||
match event {
|
||||
Event::Close => f(ItemEvent::CloseItem),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ use gpui::{
|
|||
WindowContext,
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, Button, Icon, IconButton};
|
||||
use ui::{h_stack, Icon, IconButton};
|
||||
use util::ResultExt;
|
||||
|
||||
pub trait StatusItemView: Render {
|
||||
|
@ -53,39 +53,11 @@ impl Render for StatusBar {
|
|||
.gap_4()
|
||||
.child(
|
||||
h_stack().gap_1().child(
|
||||
// TODO: Language picker
|
||||
// Feedback Tool
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(Button::new("status_buffer_language", "Rust")),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
// Github tool
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(IconButton::new("status-copilot", Icon::Copilot)),
|
||||
)
|
||||
.child(
|
||||
// Feedback Tool
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(IconButton::new("status-feedback", Icon::Envelope)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
// Bottom Dock
|
||||
h_stack().gap_1().child(
|
||||
// Terminal
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(IconButton::new("status-terminal", Icon::Terminal)),
|
||||
.child(IconButton::new("status-feedback", Icon::Envelope)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::ItemHandle;
|
||||
use gpui::{
|
||||
div, AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
|
||||
AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
|
||||
ViewContext, WindowContext,
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, v_stack, Icon, IconButton};
|
||||
use ui::{h_stack, v_stack};
|
||||
|
||||
pub enum ToolbarItemEvent {
|
||||
ChangeLocation(ToolbarItemLocation),
|
||||
|
@ -87,25 +87,7 @@ impl Render for Toolbar {
|
|||
.child(
|
||||
h_stack()
|
||||
.justify_between()
|
||||
// Toolbar left side
|
||||
.children(self.items.iter().map(|(child, _)| child.to_any()))
|
||||
// Toolbar right side
|
||||
.child(
|
||||
h_stack()
|
||||
.p_1()
|
||||
.child(
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(IconButton::new("buffer-search", Icon::MagnifyingGlass)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(IconButton::new("inline-assist", Icon::MagicWand)),
|
||||
),
|
||||
),
|
||||
.children(self.items.iter().map(|(child, _)| child.to_any())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue