In-flight entertainment (#3484)
- +language_selector2 - Language Selector 2 working! - Prevent languages showing in wrong order first - copilot_menu2 (though only tested offling, which is insufficient) - Dismiss tooltips at capture - Get ChannelModal opening [[PR Description]] Release Notes: - N/A
This commit is contained in:
commit
aa3c9b8568
19 changed files with 1257 additions and 504 deletions
39
Cargo.lock
generated
39
Cargo.lock
generated
|
@ -2143,6 +2143,25 @@ dependencies = [
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "copilot_button2"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"copilot2",
|
||||||
|
"editor2",
|
||||||
|
"fs2",
|
||||||
|
"futures 0.3.28",
|
||||||
|
"gpui2",
|
||||||
|
"language2",
|
||||||
|
"settings2",
|
||||||
|
"smol",
|
||||||
|
"theme2",
|
||||||
|
"util",
|
||||||
|
"workspace2",
|
||||||
|
"zed_actions2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -4791,6 +4810,24 @@ dependencies = [
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "language_selector2"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"editor2",
|
||||||
|
"fuzzy2",
|
||||||
|
"gpui2",
|
||||||
|
"language2",
|
||||||
|
"picker2",
|
||||||
|
"project2",
|
||||||
|
"settings2",
|
||||||
|
"theme2",
|
||||||
|
"ui2",
|
||||||
|
"util",
|
||||||
|
"workspace2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "language_tools"
|
name = "language_tools"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -11753,6 +11790,7 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"command_palette2",
|
"command_palette2",
|
||||||
"copilot2",
|
"copilot2",
|
||||||
|
"copilot_button2",
|
||||||
"ctor",
|
"ctor",
|
||||||
"db2",
|
"db2",
|
||||||
"diagnostics2",
|
"diagnostics2",
|
||||||
|
@ -11772,6 +11810,7 @@ dependencies = [
|
||||||
"isahc",
|
"isahc",
|
||||||
"journal2",
|
"journal2",
|
||||||
"language2",
|
"language2",
|
||||||
|
"language_selector2",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -61,6 +61,7 @@ members = [
|
||||||
"crates/language",
|
"crates/language",
|
||||||
"crates/language2",
|
"crates/language2",
|
||||||
"crates/language_selector",
|
"crates/language_selector",
|
||||||
|
"crates/language_selector2",
|
||||||
"crates/language_tools",
|
"crates/language_tools",
|
||||||
"crates/live_kit_client",
|
"crates/live_kit_client",
|
||||||
"crates/live_kit_server",
|
"crates/live_kit_server",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
// mod channel_modal;
|
mod channel_modal;
|
||||||
mod contact_finder;
|
mod contact_finder;
|
||||||
|
|
||||||
// use crate::{
|
// use crate::{
|
||||||
|
@ -192,6 +192,8 @@ use workspace::{
|
||||||
|
|
||||||
use crate::{face_pile::FacePile, CollaborationPanelSettings};
|
use crate::{face_pile::FacePile, CollaborationPanelSettings};
|
||||||
|
|
||||||
|
use self::channel_modal::ChannelModal;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(|workspace: &mut Workspace, _| {
|
cx.observe_new_views(|workspace: &mut Workspace, _| {
|
||||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||||
|
@ -2058,13 +2060,11 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
todo!();
|
self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx);
|
||||||
// self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
todo!();
|
self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
|
||||||
// self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
|
fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -2156,38 +2156,36 @@ impl CollabPanel {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn show_channel_modal(
|
fn show_channel_modal(
|
||||||
// &mut self,
|
&mut self,
|
||||||
// channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
// mode: channel_modal::Mode,
|
mode: channel_modal::Mode,
|
||||||
// cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
// ) {
|
) {
|
||||||
// let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
// let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
// let channel_store = self.channel_store.clone();
|
let channel_store = self.channel_store.clone();
|
||||||
// let members = self.channel_store.update(cx, |channel_store, cx| {
|
let members = self.channel_store.update(cx, |channel_store, cx| {
|
||||||
// channel_store.get_channel_member_details(channel_id, cx)
|
channel_store.get_channel_member_details(channel_id, cx)
|
||||||
// });
|
});
|
||||||
|
|
||||||
// cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
// let members = members.await?;
|
let members = members.await?;
|
||||||
// workspace.update(&mut cx, |workspace, cx| {
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
// workspace.toggle_modal(cx, |_, cx| {
|
workspace.toggle_modal(cx, |cx| {
|
||||||
// cx.add_view(|cx| {
|
ChannelModal::new(
|
||||||
// ChannelModal::new(
|
user_store.clone(),
|
||||||
// user_store.clone(),
|
channel_store.clone(),
|
||||||
// channel_store.clone(),
|
channel_id,
|
||||||
// channel_id,
|
mode,
|
||||||
// mode,
|
members,
|
||||||
// members,
|
cx,
|
||||||
// cx,
|
)
|
||||||
// )
|
});
|
||||||
// })
|
})
|
||||||
// });
|
})
|
||||||
// })
|
.detach();
|
||||||
// })
|
}
|
||||||
// .detach();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext<Self>) {
|
// fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext<Self>) {
|
||||||
// self.remove_channel(action.channel_id, cx)
|
// self.remove_channel(action.channel_id, cx)
|
||||||
|
|
|
@ -3,58 +3,54 @@ use client::{
|
||||||
proto::{self, ChannelRole, ChannelVisibility},
|
proto::{self, ChannelRole, ChannelVisibility},
|
||||||
User, UserId, UserStore,
|
User, UserId, UserStore,
|
||||||
};
|
};
|
||||||
use context_menu::{ContextMenu, ContextMenuItem};
|
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions, div, AppContext, ClipboardItem, DismissEvent, Div, Entity, EventEmitter,
|
||||||
elements::*,
|
FocusableView, Model, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext,
|
||||||
platform::{CursorStyle, MouseButton},
|
WeakView,
|
||||||
AppContext, ClipboardItem, Entity, ModelHandle, MouseState, Task, View, ViewContext,
|
|
||||||
ViewHandle,
|
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
use picker::{Picker, PickerDelegate};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use ui::v_stack;
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
use workspace::Modal;
|
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
channel_modal,
|
SelectNextControl,
|
||||||
[
|
ToggleMode,
|
||||||
SelectNextControl,
|
ToggleMemberAdmin,
|
||||||
ToggleMode,
|
RemoveMember
|
||||||
ToggleMemberAdmin,
|
|
||||||
RemoveMember
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
// pub fn init(cx: &mut AppContext) {
|
||||||
Picker::<ChannelModalDelegate>::init(cx);
|
// Picker::<ChannelModalDelegate>::init(cx);
|
||||||
cx.add_action(ChannelModal::toggle_mode);
|
// cx.add_action(ChannelModal::toggle_mode);
|
||||||
cx.add_action(ChannelModal::toggle_member_admin);
|
// cx.add_action(ChannelModal::toggle_member_admin);
|
||||||
cx.add_action(ChannelModal::remove_member);
|
// cx.add_action(ChannelModal::remove_member);
|
||||||
cx.add_action(ChannelModal::dismiss);
|
// cx.add_action(ChannelModal::dismiss);
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub struct ChannelModal {
|
pub struct ChannelModal {
|
||||||
picker: ViewHandle<Picker<ChannelModalDelegate>>,
|
picker: View<Picker<ChannelModalDelegate>>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
has_focus: bool,
|
has_focus: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelModal {
|
impl ChannelModal {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
members: Vec<ChannelMembership>,
|
members: Vec<ChannelMembership>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
|
cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
|
||||||
let picker = cx.add_view(|cx| {
|
let channel_modal = cx.view().downgrade();
|
||||||
|
let picker = cx.build_view(|cx| {
|
||||||
Picker::new(
|
Picker::new(
|
||||||
ChannelModalDelegate {
|
ChannelModalDelegate {
|
||||||
|
channel_modal,
|
||||||
matching_users: Vec::new(),
|
matching_users: Vec::new(),
|
||||||
matching_member_indices: Vec::new(),
|
matching_member_indices: Vec::new(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
|
@ -64,20 +60,17 @@ impl ChannelModal {
|
||||||
match_candidates: Vec::new(),
|
match_candidates: Vec::new(),
|
||||||
members,
|
members,
|
||||||
mode,
|
mode,
|
||||||
context_menu: cx.add_view(|cx| {
|
// context_menu: cx.add_view(|cx| {
|
||||||
let mut menu = ContextMenu::new(cx.view_id(), cx);
|
// let mut menu = ContextMenu::new(cx.view_id(), cx);
|
||||||
menu.set_position_mode(OverlayPositionMode::Local);
|
// menu.set_position_mode(OverlayPositionMode::Local);
|
||||||
menu
|
// menu
|
||||||
}),
|
// }),
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
|
let has_focus = picker.focus_handle(cx).contains_focused(cx);
|
||||||
|
|
||||||
let has_focus = picker.read(cx).has_focus();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
picker,
|
picker,
|
||||||
|
@ -88,7 +81,7 @@ impl ChannelModal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext<Self>) {
|
fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext<Self>) {
|
||||||
let mode = match self.picker.read(cx).delegate().mode {
|
let mode = match self.picker.read(cx).delegate.mode {
|
||||||
Mode::ManageMembers => Mode::InviteMembers,
|
Mode::ManageMembers => Mode::InviteMembers,
|
||||||
Mode::InviteMembers => Mode::ManageMembers,
|
Mode::InviteMembers => Mode::ManageMembers,
|
||||||
};
|
};
|
||||||
|
@ -103,20 +96,20 @@ impl ChannelModal {
|
||||||
let mut members = channel_store
|
let mut members = channel_store
|
||||||
.update(&mut cx, |channel_store, cx| {
|
.update(&mut cx, |channel_store, cx| {
|
||||||
channel_store.get_channel_member_details(channel_id, cx)
|
channel_store.get_channel_member_details(channel_id, cx)
|
||||||
})
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
|
members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.picker
|
this.picker
|
||||||
.update(cx, |picker, _| picker.delegate_mut().members = members);
|
.update(cx, |picker, _| picker.delegate.members = members);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.picker.update(cx, |picker, cx| {
|
this.picker.update(cx, |picker, cx| {
|
||||||
let delegate = picker.delegate_mut();
|
let delegate = &mut picker.delegate;
|
||||||
delegate.mode = mode;
|
delegate.mode = mode;
|
||||||
delegate.selected_index = 0;
|
delegate.selected_index = 0;
|
||||||
picker.set_query("", cx);
|
picker.set_query("", cx);
|
||||||
|
@ -131,203 +124,194 @@ impl ChannelModal {
|
||||||
|
|
||||||
fn toggle_member_admin(&mut self, _: &ToggleMemberAdmin, cx: &mut ViewContext<Self>) {
|
fn toggle_member_admin(&mut self, _: &ToggleMemberAdmin, cx: &mut ViewContext<Self>) {
|
||||||
self.picker.update(cx, |picker, cx| {
|
self.picker.update(cx, |picker, cx| {
|
||||||
picker.delegate_mut().toggle_selected_member_admin(cx);
|
picker.delegate.toggle_selected_member_admin(cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_member(&mut self, _: &RemoveMember, cx: &mut ViewContext<Self>) {
|
fn remove_member(&mut self, _: &RemoveMember, cx: &mut ViewContext<Self>) {
|
||||||
self.picker.update(cx, |picker, cx| {
|
self.picker.update(cx, |picker, cx| {
|
||||||
picker.delegate_mut().remove_selected_member(cx);
|
picker.delegate.remove_selected_member(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||||
cx.emit(PickerEvent::Dismiss);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for ChannelModal {
|
impl EventEmitter<DismissEvent> for ChannelModal {}
|
||||||
type Event = PickerEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for ChannelModal {
|
impl FocusableView for ChannelModal {
|
||||||
fn ui_name() -> &'static str {
|
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||||
"ChannelModal"
|
self.picker.focus_handle(cx)
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
|
||||||
let theme = &theme::current(cx).collab_panel.tabbed_modal;
|
|
||||||
|
|
||||||
let mode = self.picker.read(cx).delegate().mode;
|
|
||||||
let Some(channel) = self.channel_store.read(cx).channel_for_id(self.channel_id) else {
|
|
||||||
return Empty::new().into_any();
|
|
||||||
};
|
|
||||||
|
|
||||||
enum InviteMembers {}
|
|
||||||
enum ManageMembers {}
|
|
||||||
|
|
||||||
fn render_mode_button<T: 'static>(
|
|
||||||
mode: Mode,
|
|
||||||
text: &'static str,
|
|
||||||
current_mode: Mode,
|
|
||||||
theme: &theme::TabbedModal,
|
|
||||||
cx: &mut ViewContext<ChannelModal>,
|
|
||||||
) -> AnyElement<ChannelModal> {
|
|
||||||
let active = mode == current_mode;
|
|
||||||
MouseEventHandler::new::<T, _>(0, cx, move |state, _| {
|
|
||||||
let contained_text = theme.tab_button.style_for(active, state);
|
|
||||||
Label::new(text, contained_text.text.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(contained_text.container.clone())
|
|
||||||
})
|
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
if !active {
|
|
||||||
this.set_mode(mode, cx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_visibility(
|
|
||||||
channel_id: ChannelId,
|
|
||||||
visibility: ChannelVisibility,
|
|
||||||
theme: &theme::TabbedModal,
|
|
||||||
cx: &mut ViewContext<ChannelModal>,
|
|
||||||
) -> AnyElement<ChannelModal> {
|
|
||||||
enum TogglePublic {}
|
|
||||||
|
|
||||||
if visibility == ChannelVisibility::Members {
|
|
||||||
return Flex::row()
|
|
||||||
.with_child(
|
|
||||||
MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
|
|
||||||
let style = theme.visibility_toggle.style_for(state);
|
|
||||||
Label::new(format!("{}", "Public access: OFF"), style.text.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(style.container.clone())
|
|
||||||
})
|
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
this.channel_store
|
|
||||||
.update(cx, |channel_store, cx| {
|
|
||||||
channel_store.set_channel_visibility(
|
|
||||||
channel_id,
|
|
||||||
ChannelVisibility::Public,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand),
|
|
||||||
)
|
|
||||||
.into_any();
|
|
||||||
}
|
|
||||||
|
|
||||||
Flex::row()
|
|
||||||
.with_child(
|
|
||||||
MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
|
|
||||||
let style = theme.visibility_toggle.style_for(state);
|
|
||||||
Label::new(format!("{}", "Public access: ON"), style.text.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(style.container.clone())
|
|
||||||
})
|
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
this.channel_store
|
|
||||||
.update(cx, |channel_store, cx| {
|
|
||||||
channel_store.set_channel_visibility(
|
|
||||||
channel_id,
|
|
||||||
ChannelVisibility::Members,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand),
|
|
||||||
)
|
|
||||||
.with_spacing(14.0)
|
|
||||||
.with_child(
|
|
||||||
MouseEventHandler::new::<TogglePublic, _>(1, cx, move |state, _| {
|
|
||||||
let style = theme.channel_link.style_for(state);
|
|
||||||
Label::new(format!("{}", "copy link"), style.text.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(style.container.clone())
|
|
||||||
})
|
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
if let Some(channel) =
|
|
||||||
this.channel_store.read(cx).channel_for_id(channel_id)
|
|
||||||
{
|
|
||||||
let item = ClipboardItem::new(channel.link());
|
|
||||||
cx.write_to_clipboard(item);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand),
|
|
||||||
)
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
Flex::column()
|
|
||||||
.with_child(
|
|
||||||
Flex::column()
|
|
||||||
.with_child(
|
|
||||||
Label::new(format!("#{}", channel.name), theme.title.text.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.title.container.clone()),
|
|
||||||
)
|
|
||||||
.with_child(render_visibility(channel.id, channel.visibility, theme, cx))
|
|
||||||
.with_child(Flex::row().with_children([
|
|
||||||
render_mode_button::<InviteMembers>(
|
|
||||||
Mode::InviteMembers,
|
|
||||||
"Invite members",
|
|
||||||
mode,
|
|
||||||
theme,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
render_mode_button::<ManageMembers>(
|
|
||||||
Mode::ManageMembers,
|
|
||||||
"Manage members",
|
|
||||||
mode,
|
|
||||||
theme,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
]))
|
|
||||||
.expanded()
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.header),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
ChildView::new(&self.picker, cx)
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.body),
|
|
||||||
)
|
|
||||||
.constrained()
|
|
||||||
.with_max_height(theme.max_height)
|
|
||||||
.with_max_width(theme.max_width)
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.modal)
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
|
||||||
self.has_focus = true;
|
|
||||||
if cx.is_self_focused() {
|
|
||||||
cx.focus(&self.picker)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
|
|
||||||
self.has_focus = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Modal for ChannelModal {
|
impl Render for ChannelModal {
|
||||||
fn has_focus(&self) -> bool {
|
type Element = Div;
|
||||||
self.has_focus
|
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
|
v_stack().min_w_96().child(self.picker.clone())
|
||||||
|
// let theme = &theme::current(cx).collab_panel.tabbed_modal;
|
||||||
|
|
||||||
|
// let mode = self.picker.read(cx).delegate().mode;
|
||||||
|
// let Some(channel) = self.channel_store.read(cx).channel_for_id(self.channel_id) else {
|
||||||
|
// return Empty::new().into_any();
|
||||||
|
// };
|
||||||
|
|
||||||
|
// enum InviteMembers {}
|
||||||
|
// enum ManageMembers {}
|
||||||
|
|
||||||
|
// fn render_mode_button<T: 'static>(
|
||||||
|
// mode: Mode,
|
||||||
|
// text: &'static str,
|
||||||
|
// current_mode: Mode,
|
||||||
|
// theme: &theme::TabbedModal,
|
||||||
|
// cx: &mut ViewContext<ChannelModal>,
|
||||||
|
// ) -> AnyElement<ChannelModal> {
|
||||||
|
// let active = mode == current_mode;
|
||||||
|
// MouseEventHandler::new::<T, _>(0, cx, move |state, _| {
|
||||||
|
// let contained_text = theme.tab_button.style_for(active, state);
|
||||||
|
// Label::new(text, contained_text.text.clone())
|
||||||
|
// .contained()
|
||||||
|
// .with_style(contained_text.container.clone())
|
||||||
|
// })
|
||||||
|
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
// if !active {
|
||||||
|
// this.set_mode(mode, cx);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
// .into_any()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn render_visibility(
|
||||||
|
// channel_id: ChannelId,
|
||||||
|
// visibility: ChannelVisibility,
|
||||||
|
// theme: &theme::TabbedModal,
|
||||||
|
// cx: &mut ViewContext<ChannelModal>,
|
||||||
|
// ) -> AnyElement<ChannelModal> {
|
||||||
|
// enum TogglePublic {}
|
||||||
|
|
||||||
|
// if visibility == ChannelVisibility::Members {
|
||||||
|
// return Flex::row()
|
||||||
|
// .with_child(
|
||||||
|
// MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
|
||||||
|
// let style = theme.visibility_toggle.style_for(state);
|
||||||
|
// Label::new(format!("{}", "Public access: OFF"), style.text.clone())
|
||||||
|
// .contained()
|
||||||
|
// .with_style(style.container.clone())
|
||||||
|
// })
|
||||||
|
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
// this.channel_store
|
||||||
|
// .update(cx, |channel_store, cx| {
|
||||||
|
// channel_store.set_channel_visibility(
|
||||||
|
// channel_id,
|
||||||
|
// ChannelVisibility::Public,
|
||||||
|
// cx,
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
// .detach_and_log_err(cx);
|
||||||
|
// })
|
||||||
|
// .with_cursor_style(CursorStyle::PointingHand),
|
||||||
|
// )
|
||||||
|
// .into_any();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Flex::row()
|
||||||
|
// .with_child(
|
||||||
|
// MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
|
||||||
|
// let style = theme.visibility_toggle.style_for(state);
|
||||||
|
// Label::new(format!("{}", "Public access: ON"), style.text.clone())
|
||||||
|
// .contained()
|
||||||
|
// .with_style(style.container.clone())
|
||||||
|
// })
|
||||||
|
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
// this.channel_store
|
||||||
|
// .update(cx, |channel_store, cx| {
|
||||||
|
// channel_store.set_channel_visibility(
|
||||||
|
// channel_id,
|
||||||
|
// ChannelVisibility::Members,
|
||||||
|
// cx,
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
// .detach_and_log_err(cx);
|
||||||
|
// })
|
||||||
|
// .with_cursor_style(CursorStyle::PointingHand),
|
||||||
|
// )
|
||||||
|
// .with_spacing(14.0)
|
||||||
|
// .with_child(
|
||||||
|
// MouseEventHandler::new::<TogglePublic, _>(1, cx, move |state, _| {
|
||||||
|
// let style = theme.channel_link.style_for(state);
|
||||||
|
// Label::new(format!("{}", "copy link"), style.text.clone())
|
||||||
|
// .contained()
|
||||||
|
// .with_style(style.container.clone())
|
||||||
|
// })
|
||||||
|
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
// if let Some(channel) =
|
||||||
|
// this.channel_store.read(cx).channel_for_id(channel_id)
|
||||||
|
// {
|
||||||
|
// let item = ClipboardItem::new(channel.link());
|
||||||
|
// cx.write_to_clipboard(item);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .with_cursor_style(CursorStyle::PointingHand),
|
||||||
|
// )
|
||||||
|
// .into_any()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Flex::column()
|
||||||
|
// .with_child(
|
||||||
|
// Flex::column()
|
||||||
|
// .with_child(
|
||||||
|
// Label::new(format!("#{}", channel.name), theme.title.text.clone())
|
||||||
|
// .contained()
|
||||||
|
// .with_style(theme.title.container.clone()),
|
||||||
|
// )
|
||||||
|
// .with_child(render_visibility(channel.id, channel.visibility, theme, cx))
|
||||||
|
// .with_child(Flex::row().with_children([
|
||||||
|
// render_mode_button::<InviteMembers>(
|
||||||
|
// Mode::InviteMembers,
|
||||||
|
// "Invite members",
|
||||||
|
// mode,
|
||||||
|
// theme,
|
||||||
|
// cx,
|
||||||
|
// ),
|
||||||
|
// render_mode_button::<ManageMembers>(
|
||||||
|
// Mode::ManageMembers,
|
||||||
|
// "Manage members",
|
||||||
|
// mode,
|
||||||
|
// theme,
|
||||||
|
// cx,
|
||||||
|
// ),
|
||||||
|
// ]))
|
||||||
|
// .expanded()
|
||||||
|
// .contained()
|
||||||
|
// .with_style(theme.header),
|
||||||
|
// )
|
||||||
|
// .with_child(
|
||||||
|
// ChildView::new(&self.picker, cx)
|
||||||
|
// .contained()
|
||||||
|
// .with_style(theme.body),
|
||||||
|
// )
|
||||||
|
// .constrained()
|
||||||
|
// .with_max_height(theme.max_height)
|
||||||
|
// .with_max_width(theme.max_width)
|
||||||
|
// .contained()
|
||||||
|
// .with_style(theme.modal)
|
||||||
|
// .into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss_on_event(event: &Self::Event) -> bool {
|
// fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||||
match event {
|
// self.has_focus = true;
|
||||||
PickerEvent::Dismiss => true,
|
// if cx.is_self_focused() {
|
||||||
}
|
// cx.focus(&self.picker)
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
|
||||||
|
// self.has_focus = false;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
@ -337,19 +321,22 @@ pub enum Mode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChannelModalDelegate {
|
pub struct ChannelModalDelegate {
|
||||||
|
channel_modal: WeakView<ChannelModal>,
|
||||||
matching_users: Vec<Arc<User>>,
|
matching_users: Vec<Arc<User>>,
|
||||||
matching_member_indices: Vec<usize>,
|
matching_member_indices: Vec<usize>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
match_candidates: Vec<StringMatchCandidate>,
|
match_candidates: Vec<StringMatchCandidate>,
|
||||||
members: Vec<ChannelMembership>,
|
members: Vec<ChannelMembership>,
|
||||||
context_menu: ViewHandle<ContextMenu>,
|
// context_menu: ViewHandle<ContextMenu>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for ChannelModalDelegate {
|
impl PickerDelegate for ChannelModalDelegate {
|
||||||
|
type ListItem = Div;
|
||||||
|
|
||||||
fn placeholder_text(&self) -> Arc<str> {
|
fn placeholder_text(&self) -> Arc<str> {
|
||||||
"Search collaborator by username...".into()
|
"Search collaborator by username...".into()
|
||||||
}
|
}
|
||||||
|
@ -382,19 +369,19 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let matches = cx.background().block(match_strings(
|
let matches = cx.background_executor().block(match_strings(
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background().clone(),
|
cx.background_executor().clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
cx.spawn(|picker, mut cx| async move {
|
cx.spawn(|picker, mut cx| async move {
|
||||||
picker
|
picker
|
||||||
.update(&mut cx, |picker, cx| {
|
.update(&mut cx, |picker, cx| {
|
||||||
let delegate = picker.delegate_mut();
|
let delegate = &mut picker.delegate;
|
||||||
delegate.matching_member_indices.clear();
|
delegate.matching_member_indices.clear();
|
||||||
delegate
|
delegate
|
||||||
.matching_member_indices
|
.matching_member_indices
|
||||||
|
@ -412,8 +399,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||||
async {
|
async {
|
||||||
let users = search_users.await?;
|
let users = search_users.await?;
|
||||||
picker.update(&mut cx, |picker, cx| {
|
picker.update(&mut cx, |picker, cx| {
|
||||||
let delegate = picker.delegate_mut();
|
picker.delegate.matching_users = users;
|
||||||
delegate.matching_users = users;
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
@ -445,138 +431,142 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
cx.emit(PickerEvent::Dismiss);
|
self.channel_modal
|
||||||
|
.update(cx, |_, cx| {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_match(
|
fn render_match(
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
mouse_state: &mut MouseState,
|
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &gpui::AppContext,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> AnyElement<Picker<Self>> {
|
) -> Option<Self::ListItem> {
|
||||||
let full_theme = &theme::current(cx);
|
None
|
||||||
let theme = &full_theme.collab_panel.channel_modal;
|
// let full_theme = &theme::current(cx);
|
||||||
let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
|
// let theme = &full_theme.collab_panel.channel_modal;
|
||||||
let (user, role) = self.user_at_index(ix).unwrap();
|
// let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
|
||||||
let request_status = self.member_status(user.id, cx);
|
// let (user, role) = self.user_at_index(ix).unwrap();
|
||||||
|
// let request_status = self.member_status(user.id, cx);
|
||||||
|
|
||||||
let style = tabbed_modal
|
// let style = tabbed_modal
|
||||||
.picker
|
// .picker
|
||||||
.item
|
// .item
|
||||||
.in_state(selected)
|
// .in_state(selected)
|
||||||
.style_for(mouse_state);
|
// .style_for(mouse_state);
|
||||||
|
|
||||||
let in_manage = matches!(self.mode, Mode::ManageMembers);
|
// let in_manage = matches!(self.mode, Mode::ManageMembers);
|
||||||
|
|
||||||
let mut result = Flex::row()
|
// let mut result = Flex::row()
|
||||||
.with_children(user.avatar.clone().map(|avatar| {
|
// .with_children(user.avatar.clone().map(|avatar| {
|
||||||
Image::from_data(avatar)
|
// Image::from_data(avatar)
|
||||||
.with_style(theme.contact_avatar)
|
// .with_style(theme.contact_avatar)
|
||||||
.aligned()
|
// .aligned()
|
||||||
.left()
|
// .left()
|
||||||
}))
|
// }))
|
||||||
.with_child(
|
// .with_child(
|
||||||
Label::new(user.github_login.clone(), style.label.clone())
|
// Label::new(user.github_login.clone(), style.label.clone())
|
||||||
.contained()
|
// .contained()
|
||||||
.with_style(theme.contact_username)
|
// .with_style(theme.contact_username)
|
||||||
.aligned()
|
// .aligned()
|
||||||
.left(),
|
// .left(),
|
||||||
)
|
// )
|
||||||
.with_children({
|
// .with_children({
|
||||||
(in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then(
|
// (in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then(
|
||||||
|| {
|
// || {
|
||||||
Label::new("Invited", theme.member_tag.text.clone())
|
// Label::new("Invited", theme.member_tag.text.clone())
|
||||||
.contained()
|
// .contained()
|
||||||
.with_style(theme.member_tag.container)
|
// .with_style(theme.member_tag.container)
|
||||||
.aligned()
|
// .aligned()
|
||||||
.left()
|
// .left()
|
||||||
},
|
// },
|
||||||
)
|
// )
|
||||||
})
|
// })
|
||||||
.with_children(if in_manage && role == Some(ChannelRole::Admin) {
|
// .with_children(if in_manage && role == Some(ChannelRole::Admin) {
|
||||||
Some(
|
// Some(
|
||||||
Label::new("Admin", theme.member_tag.text.clone())
|
// Label::new("Admin", theme.member_tag.text.clone())
|
||||||
.contained()
|
// .contained()
|
||||||
.with_style(theme.member_tag.container)
|
// .with_style(theme.member_tag.container)
|
||||||
.aligned()
|
// .aligned()
|
||||||
.left(),
|
// .left(),
|
||||||
)
|
// )
|
||||||
} else if in_manage && role == Some(ChannelRole::Guest) {
|
// } else if in_manage && role == Some(ChannelRole::Guest) {
|
||||||
Some(
|
// Some(
|
||||||
Label::new("Guest", theme.member_tag.text.clone())
|
// Label::new("Guest", theme.member_tag.text.clone())
|
||||||
.contained()
|
// .contained()
|
||||||
.with_style(theme.member_tag.container)
|
// .with_style(theme.member_tag.container)
|
||||||
.aligned()
|
// .aligned()
|
||||||
.left(),
|
// .left(),
|
||||||
)
|
// )
|
||||||
} else {
|
// } else {
|
||||||
None
|
// None
|
||||||
})
|
// })
|
||||||
.with_children({
|
// .with_children({
|
||||||
let svg = match self.mode {
|
// let svg = match self.mode {
|
||||||
Mode::ManageMembers => Some(
|
// Mode::ManageMembers => Some(
|
||||||
Svg::new("icons/ellipsis.svg")
|
// Svg::new("icons/ellipsis.svg")
|
||||||
.with_color(theme.member_icon.color)
|
// .with_color(theme.member_icon.color)
|
||||||
.constrained()
|
// .constrained()
|
||||||
.with_width(theme.member_icon.icon_width)
|
// .with_width(theme.member_icon.icon_width)
|
||||||
.aligned()
|
// .aligned()
|
||||||
.constrained()
|
// .constrained()
|
||||||
.with_width(theme.member_icon.button_width)
|
// .with_width(theme.member_icon.button_width)
|
||||||
.with_height(theme.member_icon.button_width)
|
// .with_height(theme.member_icon.button_width)
|
||||||
.contained()
|
// .contained()
|
||||||
.with_style(theme.member_icon.container),
|
// .with_style(theme.member_icon.container),
|
||||||
),
|
// ),
|
||||||
Mode::InviteMembers => match request_status {
|
// Mode::InviteMembers => match request_status {
|
||||||
Some(proto::channel_member::Kind::Member) => Some(
|
// Some(proto::channel_member::Kind::Member) => Some(
|
||||||
Svg::new("icons/check.svg")
|
// Svg::new("icons/check.svg")
|
||||||
.with_color(theme.member_icon.color)
|
// .with_color(theme.member_icon.color)
|
||||||
.constrained()
|
// .constrained()
|
||||||
.with_width(theme.member_icon.icon_width)
|
// .with_width(theme.member_icon.icon_width)
|
||||||
.aligned()
|
// .aligned()
|
||||||
.constrained()
|
// .constrained()
|
||||||
.with_width(theme.member_icon.button_width)
|
// .with_width(theme.member_icon.button_width)
|
||||||
.with_height(theme.member_icon.button_width)
|
// .with_height(theme.member_icon.button_width)
|
||||||
.contained()
|
// .contained()
|
||||||
.with_style(theme.member_icon.container),
|
// .with_style(theme.member_icon.container),
|
||||||
),
|
// ),
|
||||||
Some(proto::channel_member::Kind::Invitee) => Some(
|
// Some(proto::channel_member::Kind::Invitee) => Some(
|
||||||
Svg::new("icons/check.svg")
|
// Svg::new("icons/check.svg")
|
||||||
.with_color(theme.invitee_icon.color)
|
// .with_color(theme.invitee_icon.color)
|
||||||
.constrained()
|
// .constrained()
|
||||||
.with_width(theme.invitee_icon.icon_width)
|
// .with_width(theme.invitee_icon.icon_width)
|
||||||
.aligned()
|
// .aligned()
|
||||||
.constrained()
|
// .constrained()
|
||||||
.with_width(theme.invitee_icon.button_width)
|
// .with_width(theme.invitee_icon.button_width)
|
||||||
.with_height(theme.invitee_icon.button_width)
|
// .with_height(theme.invitee_icon.button_width)
|
||||||
.contained()
|
// .contained()
|
||||||
.with_style(theme.invitee_icon.container),
|
// .with_style(theme.invitee_icon.container),
|
||||||
),
|
// ),
|
||||||
Some(proto::channel_member::Kind::AncestorMember) | None => None,
|
// Some(proto::channel_member::Kind::AncestorMember) | None => None,
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
|
|
||||||
svg.map(|svg| svg.aligned().flex_float().into_any())
|
// svg.map(|svg| svg.aligned().flex_float().into_any())
|
||||||
})
|
// })
|
||||||
.contained()
|
// .contained()
|
||||||
.with_style(style.container)
|
// .with_style(style.container)
|
||||||
.constrained()
|
// .constrained()
|
||||||
.with_height(tabbed_modal.row_height)
|
// .with_height(tabbed_modal.row_height)
|
||||||
.into_any();
|
// .into_any();
|
||||||
|
|
||||||
if selected {
|
// if selected {
|
||||||
result = Stack::new()
|
// result = Stack::new()
|
||||||
.with_child(result)
|
// .with_child(result)
|
||||||
.with_child(
|
// .with_child(
|
||||||
ChildView::new(&self.context_menu, cx)
|
// ChildView::new(&self.context_menu, cx)
|
||||||
.aligned()
|
// .aligned()
|
||||||
.top()
|
// .top()
|
||||||
.right(),
|
// .right(),
|
||||||
)
|
// )
|
||||||
.into_any();
|
// .into_any();
|
||||||
}
|
// }
|
||||||
|
|
||||||
result
|
// result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -623,7 +613,7 @@ impl ChannelModalDelegate {
|
||||||
cx.spawn(|picker, mut cx| async move {
|
cx.spawn(|picker, mut cx| async move {
|
||||||
update.await?;
|
update.await?;
|
||||||
picker.update(&mut cx, |picker, cx| {
|
picker.update(&mut cx, |picker, cx| {
|
||||||
let this = picker.delegate_mut();
|
let this = &mut picker.delegate;
|
||||||
if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user.id) {
|
if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user.id) {
|
||||||
member.role = new_role;
|
member.role = new_role;
|
||||||
}
|
}
|
||||||
|
@ -644,7 +634,7 @@ impl ChannelModalDelegate {
|
||||||
cx.spawn(|picker, mut cx| async move {
|
cx.spawn(|picker, mut cx| async move {
|
||||||
update.await?;
|
update.await?;
|
||||||
picker.update(&mut cx, |picker, cx| {
|
picker.update(&mut cx, |picker, cx| {
|
||||||
let this = picker.delegate_mut();
|
let this = &mut picker.delegate;
|
||||||
if let Some(ix) = this.members.iter_mut().position(|m| m.user.id == user_id) {
|
if let Some(ix) = this.members.iter_mut().position(|m| m.user.id == user_id) {
|
||||||
this.members.remove(ix);
|
this.members.remove(ix);
|
||||||
this.matching_member_indices.retain_mut(|member_ix| {
|
this.matching_member_indices.retain_mut(|member_ix| {
|
||||||
|
@ -683,7 +673,7 @@ impl ChannelModalDelegate {
|
||||||
kind: proto::channel_member::Kind::Invitee,
|
kind: proto::channel_member::Kind::Invitee,
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
};
|
};
|
||||||
let members = &mut this.delegate_mut().members;
|
let members = &mut this.delegate.members;
|
||||||
match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) {
|
match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) {
|
||||||
Ok(ix) | Err(ix) => members.insert(ix, new_member),
|
Ok(ix) | Err(ix) => members.insert(ix, new_member),
|
||||||
}
|
}
|
||||||
|
@ -695,23 +685,23 @@ impl ChannelModalDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_context_menu(&mut self, role: ChannelRole, cx: &mut ViewContext<Picker<Self>>) {
|
fn show_context_menu(&mut self, role: ChannelRole, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
self.context_menu.update(cx, |context_menu, cx| {
|
// self.context_menu.update(cx, |context_menu, cx| {
|
||||||
context_menu.show(
|
// context_menu.show(
|
||||||
Default::default(),
|
// Default::default(),
|
||||||
AnchorCorner::TopRight,
|
// AnchorCorner::TopRight,
|
||||||
vec![
|
// vec![
|
||||||
ContextMenuItem::action("Remove", RemoveMember),
|
// ContextMenuItem::action("Remove", RemoveMember),
|
||||||
ContextMenuItem::action(
|
// ContextMenuItem::action(
|
||||||
if role == ChannelRole::Admin {
|
// if role == ChannelRole::Admin {
|
||||||
"Make non-admin"
|
// "Make non-admin"
|
||||||
} else {
|
// } else {
|
||||||
"Make admin"
|
// "Make admin"
|
||||||
},
|
// },
|
||||||
ToggleMemberAdmin,
|
// ToggleMemberAdmin,
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
cx,
|
// cx,
|
||||||
)
|
// )
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
27
crates/copilot_button2/Cargo.toml
Normal file
27
crates/copilot_button2/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
[package]
|
||||||
|
name = "copilot_button2"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/copilot_button.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
copilot = { package = "copilot2", path = "../copilot2" }
|
||||||
|
editor = { package = "editor2", path = "../editor2" }
|
||||||
|
fs = { package = "fs2", path = "../fs2" }
|
||||||
|
zed-actions = { package="zed_actions2", path = "../zed_actions2"}
|
||||||
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
language = { package = "language2", path = "../language2" }
|
||||||
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
|
util = { path = "../util" }
|
||||||
|
workspace = { package = "workspace2", path = "../workspace2" }
|
||||||
|
anyhow.workspace = true
|
||||||
|
smol.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
|
371
crates/copilot_button2/src/copilot_button.rs
Normal file
371
crates/copilot_button2/src/copilot_button.rs
Normal file
|
@ -0,0 +1,371 @@
|
||||||
|
#![allow(unused)]
|
||||||
|
use anyhow::Result;
|
||||||
|
use copilot::{Copilot, SignOut, Status};
|
||||||
|
use editor::{scroll::autoscroll::Autoscroll, Editor};
|
||||||
|
use fs::Fs;
|
||||||
|
use gpui::{
|
||||||
|
div, Action, AnchorCorner, AppContext, AsyncAppContext, AsyncWindowContext, Div, Entity,
|
||||||
|
ParentElement, Render, Subscription, View, ViewContext, WeakView, WindowContext,
|
||||||
|
};
|
||||||
|
use language::{
|
||||||
|
language_settings::{self, all_language_settings, AllLanguageSettings},
|
||||||
|
File, Language,
|
||||||
|
};
|
||||||
|
use settings::{update_settings_file, Settings, SettingsStore};
|
||||||
|
use std::{path::Path, sync::Arc};
|
||||||
|
use util::{paths, ResultExt};
|
||||||
|
use workspace::{
|
||||||
|
create_and_open_local_file,
|
||||||
|
item::ItemHandle,
|
||||||
|
ui::{
|
||||||
|
popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, PopoverMenu, Tooltip,
|
||||||
|
},
|
||||||
|
StatusItemView, Toast, Workspace,
|
||||||
|
};
|
||||||
|
use zed_actions::OpenBrowser;
|
||||||
|
|
||||||
|
const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
|
||||||
|
const COPILOT_STARTING_TOAST_ID: usize = 1337;
|
||||||
|
const COPILOT_ERROR_TOAST_ID: usize = 1338;
|
||||||
|
|
||||||
|
pub struct CopilotButton {
|
||||||
|
editor_subscription: Option<(Subscription, usize)>,
|
||||||
|
editor_enabled: Option<bool>,
|
||||||
|
language: Option<Arc<Language>>,
|
||||||
|
file: Option<Arc<dyn File>>,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for CopilotButton {
|
||||||
|
type Element = Div;
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
|
let all_language_settings = all_language_settings(None, cx);
|
||||||
|
if !all_language_settings.copilot.feature_enabled {
|
||||||
|
return div();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(copilot) = Copilot::global(cx) else {
|
||||||
|
return div();
|
||||||
|
};
|
||||||
|
let status = copilot.read(cx).status();
|
||||||
|
|
||||||
|
let enabled = self
|
||||||
|
.editor_enabled
|
||||||
|
.unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
|
||||||
|
|
||||||
|
let icon = match status {
|
||||||
|
Status::Error(_) => Icon::CopilotError,
|
||||||
|
Status::Authorized => {
|
||||||
|
if enabled {
|
||||||
|
Icon::Copilot
|
||||||
|
} else {
|
||||||
|
Icon::CopilotDisabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Icon::CopilotInit,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Status::Error(e) = status {
|
||||||
|
return div().child(
|
||||||
|
IconButton::new("copilot-error", icon)
|
||||||
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
|
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.show_toast(
|
||||||
|
Toast::new(
|
||||||
|
COPILOT_ERROR_TOAST_ID,
|
||||||
|
format!("Copilot can't be started: {}", e),
|
||||||
|
)
|
||||||
|
.on_click(
|
||||||
|
"Reinstall Copilot",
|
||||||
|
|cx| {
|
||||||
|
if let Some(copilot) = Copilot::global(cx) {
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| copilot.reinstall(cx))
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let this = cx.view().clone();
|
||||||
|
|
||||||
|
div().child(
|
||||||
|
popover_menu("copilot")
|
||||||
|
.menu(move |cx| match status {
|
||||||
|
Status::Authorized => this.update(cx, |this, cx| this.build_copilot_menu(cx)),
|
||||||
|
_ => this.update(cx, |this, cx| this.build_copilot_start_menu(cx)),
|
||||||
|
})
|
||||||
|
.anchor(AnchorCorner::BottomRight)
|
||||||
|
.trigger(
|
||||||
|
IconButton::new("copilot-icon", icon)
|
||||||
|
.tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CopilotButton {
|
||||||
|
pub fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
|
||||||
|
|
||||||
|
cx.observe_global::<SettingsStore>(move |_, cx| cx.notify())
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
editor_subscription: None,
|
||||||
|
editor_enabled: None,
|
||||||
|
language: None,
|
||||||
|
file: None,
|
||||||
|
fs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
|
||||||
|
let fs = self.fs.clone();
|
||||||
|
ContextMenu::build(cx, |menu, cx| {
|
||||||
|
menu.entry("Sign In", initiate_sign_in)
|
||||||
|
.entry("Disable Copilot", move |cx| hide_copilot(fs.clone(), cx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_copilot_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
|
||||||
|
let fs = self.fs.clone();
|
||||||
|
|
||||||
|
return ContextMenu::build(cx, move |mut menu, cx| {
|
||||||
|
if let Some(language) = self.language.clone() {
|
||||||
|
let fs = fs.clone();
|
||||||
|
let language_enabled =
|
||||||
|
language_settings::language_settings(Some(&language), None, cx)
|
||||||
|
.show_copilot_suggestions;
|
||||||
|
|
||||||
|
menu = menu.entry(
|
||||||
|
format!(
|
||||||
|
"{} Suggestions for {}",
|
||||||
|
if language_enabled { "Hide" } else { "Show" },
|
||||||
|
language.name()
|
||||||
|
),
|
||||||
|
move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let settings = AllLanguageSettings::get_global(cx);
|
||||||
|
|
||||||
|
if let Some(file) = &self.file {
|
||||||
|
let path = file.path().clone();
|
||||||
|
let path_enabled = settings.copilot_enabled_for_path(&path);
|
||||||
|
|
||||||
|
menu = menu.entry(
|
||||||
|
format!(
|
||||||
|
"{} Suggestions for This Path",
|
||||||
|
if path_enabled { "Hide" } else { "Show" }
|
||||||
|
),
|
||||||
|
move |cx| {
|
||||||
|
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
|
||||||
|
if let Ok(workspace) = workspace.root_view(cx) {
|
||||||
|
let workspace = workspace.downgrade();
|
||||||
|
cx.spawn(|cx| {
|
||||||
|
configure_disabled_globs(
|
||||||
|
workspace,
|
||||||
|
path_enabled.then_some(path.clone()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let globally_enabled = settings.copilot_enabled(None, None);
|
||||||
|
menu.entry(
|
||||||
|
if globally_enabled {
|
||||||
|
"Hide Suggestions for All Files"
|
||||||
|
} else {
|
||||||
|
"Show Suggestions for All Files"
|
||||||
|
},
|
||||||
|
move |cx| toggle_copilot_globally(fs.clone(), cx),
|
||||||
|
)
|
||||||
|
.separator()
|
||||||
|
.link(
|
||||||
|
"Copilot Settings",
|
||||||
|
OpenBrowser {
|
||||||
|
url: COPILOT_SETTINGS_URL.to_string(),
|
||||||
|
}
|
||||||
|
.boxed_clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.action("Sign Out", SignOut.boxed_clone(), cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_enabled(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
|
||||||
|
let editor = editor.read(cx);
|
||||||
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
let suggestion_anchor = editor.selections.newest_anchor().start;
|
||||||
|
let language = snapshot.language_at(suggestion_anchor);
|
||||||
|
let file = snapshot.file_at(suggestion_anchor).cloned();
|
||||||
|
|
||||||
|
self.editor_enabled = Some(
|
||||||
|
all_language_settings(self.file.as_ref(), cx)
|
||||||
|
.copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())),
|
||||||
|
);
|
||||||
|
self.language = language.cloned();
|
||||||
|
self.file = file;
|
||||||
|
|
||||||
|
cx.notify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusItemView for CopilotButton {
|
||||||
|
fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(editor) = item.map(|item| item.act_as::<Editor>(cx)).flatten() {
|
||||||
|
self.editor_subscription = Some((
|
||||||
|
cx.observe(&editor, Self::update_enabled),
|
||||||
|
editor.entity_id().as_u64() as usize,
|
||||||
|
));
|
||||||
|
self.update_enabled(editor, cx);
|
||||||
|
} else {
|
||||||
|
self.language = None;
|
||||||
|
self.editor_subscription = None;
|
||||||
|
self.editor_enabled = None;
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn configure_disabled_globs(
|
||||||
|
workspace: WeakView<Workspace>,
|
||||||
|
path_to_disable: Option<Arc<Path>>,
|
||||||
|
mut cx: AsyncWindowContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let settings_editor = workspace
|
||||||
|
.update(&mut cx, |_, cx| {
|
||||||
|
create_and_open_local_file(&paths::SETTINGS, cx, || {
|
||||||
|
settings::initial_user_settings_content().as_ref().into()
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.await?
|
||||||
|
.downcast::<Editor>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
settings_editor.downgrade().update(&mut cx, |item, cx| {
|
||||||
|
let text = item.buffer().read(cx).snapshot(cx).text();
|
||||||
|
|
||||||
|
let settings = cx.global::<SettingsStore>();
|
||||||
|
let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
|
||||||
|
let copilot = file.copilot.get_or_insert_with(Default::default);
|
||||||
|
let globs = copilot.disabled_globs.get_or_insert_with(|| {
|
||||||
|
settings
|
||||||
|
.get::<AllLanguageSettings>(None)
|
||||||
|
.copilot
|
||||||
|
.disabled_globs
|
||||||
|
.iter()
|
||||||
|
.map(|glob| glob.glob().to_string())
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(path_to_disable) = &path_to_disable {
|
||||||
|
globs.push(path_to_disable.to_string_lossy().into_owned());
|
||||||
|
} else {
|
||||||
|
globs.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !edits.is_empty() {
|
||||||
|
item.change_selections(Some(Autoscroll::newest()), cx, |selections| {
|
||||||
|
selections.select_ranges(edits.iter().map(|e| e.0.clone()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// When *enabling* a path, don't actually perform an edit, just select the range.
|
||||||
|
if path_to_disable.is_some() {
|
||||||
|
item.edit(edits.iter().cloned(), cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_copilot_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||||
|
let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None);
|
||||||
|
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
|
||||||
|
file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_copilot_for_language(language: Arc<Language>, fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||||
|
let show_copilot_suggestions =
|
||||||
|
all_language_settings(None, cx).copilot_enabled(Some(&language), None);
|
||||||
|
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
|
||||||
|
file.languages
|
||||||
|
.entry(language.name())
|
||||||
|
.or_default()
|
||||||
|
.show_copilot_suggestions = Some(!show_copilot_suggestions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||||
|
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
|
||||||
|
file.features.get_or_insert(Default::default()).copilot = Some(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initiate_sign_in(cx: &mut WindowContext) {
|
||||||
|
let Some(copilot) = Copilot::global(cx) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let status = copilot.read(cx).status();
|
||||||
|
|
||||||
|
match status {
|
||||||
|
Status::Starting { task } => {
|
||||||
|
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(workspace) = workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.show_toast(
|
||||||
|
Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot is starting..."),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
workspace.weak_handle()
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.spawn(|mut cx| async move {
|
||||||
|
task.await;
|
||||||
|
if let Some(copilot) = cx.update(|_, cx| Copilot::global(cx)).ok().flatten() {
|
||||||
|
workspace
|
||||||
|
.update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
|
||||||
|
Status::Authorized => workspace.show_toast(
|
||||||
|
Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot has started!"),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
workspace.dismiss_toast(COPILOT_STARTING_TOAST_ID, cx);
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1920,14 +1920,14 @@ impl Editor {
|
||||||
// self.buffer.read(cx).read(cx).file_at(point).cloned()
|
// self.buffer.read(cx).read(cx).file_at(point).cloned()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// pub fn active_excerpt(
|
pub fn active_excerpt(
|
||||||
// &self,
|
&self,
|
||||||
// cx: &AppContext,
|
cx: &AppContext,
|
||||||
// ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
|
) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
|
||||||
// self.buffer
|
self.buffer
|
||||||
// .read(cx)
|
.read(cx)
|
||||||
// .excerpt_containing(self.selections.newest_anchor().head(), cx)
|
.excerpt_containing(self.selections.newest_anchor().head(), cx)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn style(&self, cx: &AppContext) -> EditorStyle {
|
// pub fn style(&self, cx: &AppContext) -> EditorStyle {
|
||||||
// build_style(
|
// build_style(
|
||||||
|
|
|
@ -992,10 +992,6 @@ impl Interactivity {
|
||||||
let interactive_bounds = interactive_bounds.clone();
|
let interactive_bounds = interactive_bounds.clone();
|
||||||
|
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
if phase != DispatchPhase::Bubble {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
|
let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
|
||||||
&& pending_mouse_down.borrow().is_none();
|
&& pending_mouse_down.borrow().is_none();
|
||||||
if !is_hovered {
|
if !is_hovered {
|
||||||
|
@ -1003,6 +999,10 @@ impl Interactivity {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if phase != DispatchPhase::Bubble {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if active_tooltip.borrow().is_none() {
|
if active_tooltip.borrow().is_none() {
|
||||||
let task = cx.spawn({
|
let task = cx.spawn({
|
||||||
let active_tooltip = active_tooltip.clone();
|
let active_tooltip = active_tooltip.clone();
|
||||||
|
|
26
crates/language_selector2/Cargo.toml
Normal file
26
crates/language_selector2/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "language_selector2"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/language_selector.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
editor = { package = "editor2", path = "../editor2" }
|
||||||
|
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||||
|
language = { package = "language2", path = "../language2" }
|
||||||
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
picker = { package = "picker2", path = "../picker2" }
|
||||||
|
project = { package = "project2", path = "../project2" }
|
||||||
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
|
util = { path = "../util" }
|
||||||
|
workspace = { package = "workspace2", path = "../workspace2" }
|
||||||
|
anyhow.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
|
82
crates/language_selector2/src/active_buffer_language.rs
Normal file
82
crates/language_selector2/src/active_buffer_language.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use editor::Editor;
|
||||||
|
use gpui::{
|
||||||
|
div, Div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ui::{Button, ButtonCommon, Clickable, Tooltip};
|
||||||
|
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||||
|
|
||||||
|
use crate::LanguageSelector;
|
||||||
|
|
||||||
|
pub struct ActiveBufferLanguage {
|
||||||
|
active_language: Option<Option<Arc<str>>>,
|
||||||
|
workspace: WeakView<Workspace>,
|
||||||
|
_observe_active_editor: Option<Subscription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveBufferLanguage {
|
||||||
|
pub fn new(workspace: &Workspace) -> Self {
|
||||||
|
Self {
|
||||||
|
active_language: None,
|
||||||
|
workspace: workspace.weak_handle(),
|
||||||
|
_observe_active_editor: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_language(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
|
||||||
|
self.active_language = Some(None);
|
||||||
|
|
||||||
|
let editor = editor.read(cx);
|
||||||
|
if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
|
||||||
|
if let Some(language) = buffer.read(cx).language() {
|
||||||
|
self.active_language = Some(Some(language.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ActiveBufferLanguage {
|
||||||
|
type Element = Div;
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div {
|
||||||
|
div().when_some(self.active_language.as_ref(), |el, active_language| {
|
||||||
|
let active_language_text = if let Some(active_language_text) = active_language {
|
||||||
|
active_language_text.to_string()
|
||||||
|
} else {
|
||||||
|
"Unknown".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
el.child(
|
||||||
|
Button::new("change-language", active_language_text)
|
||||||
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
|
if let Some(workspace) = this.workspace.upgrade() {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
LanguageSelector::toggle(workspace, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.tooltip(|cx| Tooltip::text("Select Language", cx)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusItemView for ActiveBufferLanguage {
|
||||||
|
fn set_active_pane_item(
|
||||||
|
&mut self,
|
||||||
|
active_pane_item: Option<&dyn ItemHandle>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
|
||||||
|
self._observe_active_editor = Some(cx.observe(&editor, Self::update_language));
|
||||||
|
self.update_language(editor, cx);
|
||||||
|
} else {
|
||||||
|
self.active_language = None;
|
||||||
|
self._observe_active_editor = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
231
crates/language_selector2/src/language_selector.rs
Normal file
231
crates/language_selector2/src/language_selector.rs
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
mod active_buffer_language;
|
||||||
|
|
||||||
|
pub use active_buffer_language::ActiveBufferLanguage;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use editor::Editor;
|
||||||
|
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||||
|
use gpui::{
|
||||||
|
actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model,
|
||||||
|
ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
|
||||||
|
};
|
||||||
|
use language::{Buffer, LanguageRegistry};
|
||||||
|
use picker::{Picker, PickerDelegate};
|
||||||
|
use project::Project;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ui::{v_stack, HighlightedLabel, ListItem, Selectable};
|
||||||
|
use util::ResultExt;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
actions!(Toggle);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
cx.observe_new_views(LanguageSelector::register).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LanguageSelector {
|
||||||
|
picker: View<Picker<LanguageSelectorDelegate>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageSelector {
|
||||||
|
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
||||||
|
workspace.register_action(move |workspace, _: &Toggle, cx| {
|
||||||
|
Self::toggle(workspace, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<()> {
|
||||||
|
let registry = workspace.app_state().languages.clone();
|
||||||
|
let (_, buffer, _) = workspace
|
||||||
|
.active_item(cx)?
|
||||||
|
.act_as::<Editor>(cx)?
|
||||||
|
.read(cx)
|
||||||
|
.active_excerpt(cx)?;
|
||||||
|
let project = workspace.project().clone();
|
||||||
|
|
||||||
|
workspace.toggle_modal(cx, move |cx| {
|
||||||
|
LanguageSelector::new(buffer, project, registry, cx)
|
||||||
|
});
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
project: Model<Project>,
|
||||||
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let delegate = LanguageSelectorDelegate::new(
|
||||||
|
cx.view().downgrade(),
|
||||||
|
buffer,
|
||||||
|
project,
|
||||||
|
language_registry,
|
||||||
|
);
|
||||||
|
|
||||||
|
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
|
||||||
|
Self { picker }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for LanguageSelector {
|
||||||
|
type Element = Div;
|
||||||
|
|
||||||
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
|
v_stack().min_w_96().child(self.picker.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusableView for LanguageSelector {
|
||||||
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
|
self.picker.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl EventEmitter<DismissEvent> for LanguageSelector {}
|
||||||
|
|
||||||
|
pub struct LanguageSelectorDelegate {
|
||||||
|
language_selector: WeakView<LanguageSelector>,
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
project: Model<Project>,
|
||||||
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
candidates: Vec<StringMatchCandidate>,
|
||||||
|
matches: Vec<StringMatch>,
|
||||||
|
selected_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageSelectorDelegate {
|
||||||
|
fn new(
|
||||||
|
language_selector: WeakView<LanguageSelector>,
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
project: Model<Project>,
|
||||||
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
) -> Self {
|
||||||
|
let candidates = language_registry
|
||||||
|
.language_names()
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
language_selector,
|
||||||
|
buffer,
|
||||||
|
project,
|
||||||
|
language_registry,
|
||||||
|
candidates,
|
||||||
|
matches: vec![],
|
||||||
|
selected_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PickerDelegate for LanguageSelectorDelegate {
|
||||||
|
type ListItem = ListItem;
|
||||||
|
|
||||||
|
fn placeholder_text(&self) -> Arc<str> {
|
||||||
|
"Select a language...".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_count(&self) -> usize {
|
||||||
|
self.matches.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
|
if let Some(mat) = self.matches.get(self.selected_index) {
|
||||||
|
let language_name = &self.candidates[mat.candidate_id].string;
|
||||||
|
let language = self.language_registry.language_for_name(language_name);
|
||||||
|
let project = self.project.downgrade();
|
||||||
|
let buffer = self.buffer.downgrade();
|
||||||
|
cx.spawn(|_, mut cx| async move {
|
||||||
|
let language = language.await?;
|
||||||
|
let project = project
|
||||||
|
.upgrade()
|
||||||
|
.ok_or_else(|| anyhow!("project was dropped"))?;
|
||||||
|
let buffer = buffer
|
||||||
|
.upgrade()
|
||||||
|
.ok_or_else(|| anyhow!("buffer was dropped"))?;
|
||||||
|
project.update(&mut cx, |project, cx| {
|
||||||
|
project.set_language_for_buffer(&buffer, language, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
self.dismissed(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
|
self.language_selector
|
||||||
|
.update(cx, |_, cx| cx.emit(DismissEvent))
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_index(&self) -> usize {
|
||||||
|
self.selected_index
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
|
||||||
|
self.selected_index = ix;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_matches(
|
||||||
|
&mut self,
|
||||||
|
query: String,
|
||||||
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
|
) -> gpui::Task<()> {
|
||||||
|
let background = cx.background_executor().clone();
|
||||||
|
let candidates = self.candidates.clone();
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let matches = if query.is_empty() {
|
||||||
|
candidates
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, candidate)| StringMatch {
|
||||||
|
candidate_id: index,
|
||||||
|
string: candidate.string,
|
||||||
|
positions: Vec::new(),
|
||||||
|
score: 0.0,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
match_strings(
|
||||||
|
&candidates,
|
||||||
|
&query,
|
||||||
|
false,
|
||||||
|
100,
|
||||||
|
&Default::default(),
|
||||||
|
background,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let delegate = &mut this.delegate;
|
||||||
|
delegate.matches = matches;
|
||||||
|
delegate.selected_index = delegate
|
||||||
|
.selected_index
|
||||||
|
.min(delegate.matches.len().saturating_sub(1));
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_match(
|
||||||
|
&self,
|
||||||
|
ix: usize,
|
||||||
|
selected: bool,
|
||||||
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
|
) -> Option<Self::ListItem> {
|
||||||
|
let mat = &self.matches[ix];
|
||||||
|
let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
|
||||||
|
let mut label = mat.string.clone();
|
||||||
|
if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
|
||||||
|
label.push_str(" (current)");
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(
|
||||||
|
ListItem::new(ix)
|
||||||
|
.inset(true)
|
||||||
|
.selected(selected)
|
||||||
|
.child(HighlightedLabel::new(label, mat.positions.clone())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -178,6 +178,15 @@ impl<D: PickerDelegate> Picker<D> {
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn query(&self, cx: &AppContext) -> String {
|
||||||
|
self.editor.read(cx).text(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_query(&self, query: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
|
||||||
|
self.editor
|
||||||
|
.update(cx, |editor, cx| editor.set_text(query, cx));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: PickerDelegate> Render for Picker<D> {
|
impl<D: PickerDelegate> Render for Picker<D> {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
h_stack, prelude::*, v_stack, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader,
|
h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem,
|
||||||
|
ListSeparator, ListSubHeader,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
px, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
|
px, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
|
||||||
|
@ -13,6 +14,7 @@ pub enum ContextMenuItem {
|
||||||
Header(SharedString),
|
Header(SharedString),
|
||||||
Entry {
|
Entry {
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
|
icon: Option<Icon>,
|
||||||
handler: Rc<dyn Fn(&mut WindowContext)>,
|
handler: Rc<dyn Fn(&mut WindowContext)>,
|
||||||
key_binding: Option<KeyBinding>,
|
key_binding: Option<KeyBinding>,
|
||||||
},
|
},
|
||||||
|
@ -69,6 +71,7 @@ impl ContextMenu {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
handler: Rc::new(on_click),
|
handler: Rc::new(on_click),
|
||||||
key_binding: None,
|
key_binding: None,
|
||||||
|
icon: None,
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -83,6 +86,22 @@ impl ContextMenu {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
key_binding: KeyBinding::for_action(&*action, cx),
|
key_binding: KeyBinding::for_action(&*action, cx),
|
||||||
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
|
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
|
||||||
|
icon: None,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link(
|
||||||
|
mut self,
|
||||||
|
label: impl Into<SharedString>,
|
||||||
|
action: Box<dyn Action>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Self {
|
||||||
|
self.items.push(ContextMenuItem::Entry {
|
||||||
|
label: label.into(),
|
||||||
|
key_binding: KeyBinding::for_action(&*action, cx),
|
||||||
|
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
|
||||||
|
icon: Some(Icon::Link),
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -175,19 +194,30 @@ impl Render for ContextMenu {
|
||||||
ListSubHeader::new(header.clone()).into_any_element()
|
ListSubHeader::new(header.clone()).into_any_element()
|
||||||
}
|
}
|
||||||
ContextMenuItem::Entry {
|
ContextMenuItem::Entry {
|
||||||
label: entry,
|
label,
|
||||||
handler: callback,
|
handler,
|
||||||
key_binding,
|
key_binding,
|
||||||
|
icon,
|
||||||
} => {
|
} => {
|
||||||
let callback = callback.clone();
|
let handler = handler.clone();
|
||||||
let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent));
|
let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent));
|
||||||
|
|
||||||
ListItem::new(entry.clone())
|
let label_element = if let Some(icon) = icon {
|
||||||
|
h_stack()
|
||||||
|
.gap_1()
|
||||||
|
.child(Label::new(label.clone()))
|
||||||
|
.child(IconElement::new(*icon))
|
||||||
|
.into_any_element()
|
||||||
|
} else {
|
||||||
|
Label::new(label.clone()).into_any_element()
|
||||||
|
};
|
||||||
|
|
||||||
|
ListItem::new(label.clone())
|
||||||
.child(
|
.child(
|
||||||
h_stack()
|
h_stack()
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(Label::new(entry.clone()))
|
.child(label_element)
|
||||||
.children(
|
.children(
|
||||||
key_binding
|
key_binding
|
||||||
.clone()
|
.clone()
|
||||||
|
@ -196,7 +226,7 @@ impl Render for ContextMenu {
|
||||||
)
|
)
|
||||||
.selected(Some(ix) == self.selected_index)
|
.selected(Some(ix) == self.selected_index)
|
||||||
.on_click(move |event, cx| {
|
.on_click(move |event, cx| {
|
||||||
callback(cx);
|
handler(cx);
|
||||||
dismiss(event, cx)
|
dismiss(event, cx)
|
||||||
})
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
|
|
|
@ -54,6 +54,7 @@ pub enum Icon {
|
||||||
FolderX,
|
FolderX,
|
||||||
Hash,
|
Hash,
|
||||||
InlayHint,
|
InlayHint,
|
||||||
|
Link,
|
||||||
MagicWand,
|
MagicWand,
|
||||||
MagnifyingGlass,
|
MagnifyingGlass,
|
||||||
MailOpen,
|
MailOpen,
|
||||||
|
@ -126,6 +127,7 @@ impl Icon {
|
||||||
Icon::FolderX => "icons/stop_sharing.svg",
|
Icon::FolderX => "icons/stop_sharing.svg",
|
||||||
Icon::Hash => "icons/hash.svg",
|
Icon::Hash => "icons/hash.svg",
|
||||||
Icon::InlayHint => "icons/inlay_hint.svg",
|
Icon::InlayHint => "icons/inlay_hint.svg",
|
||||||
|
Icon::Link => "icons/link.svg",
|
||||||
Icon::MagicWand => "icons/magic-wand.svg",
|
Icon::MagicWand => "icons/magic-wand.svg",
|
||||||
Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
|
Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
|
||||||
Icon::MailOpen => "icons/mail-open.svg",
|
Icon::MailOpen => "icons/mail-open.svg",
|
||||||
|
|
|
@ -135,24 +135,22 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
|
pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
|
||||||
todo!()
|
self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
|
||||||
// self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
|
self.show_notification(toast.id, cx, |cx| {
|
||||||
// self.show_notification(toast.id, cx, |cx| {
|
cx.build_view(|_cx| match toast.on_click.as_ref() {
|
||||||
// cx.add_view(|_cx| match toast.on_click.as_ref() {
|
Some((click_msg, on_click)) => {
|
||||||
// Some((click_msg, on_click)) => {
|
let on_click = on_click.clone();
|
||||||
// let on_click = on_click.clone();
|
simple_message_notification::MessageNotification::new(toast.msg.clone())
|
||||||
// simple_message_notification::MessageNotification::new(toast.msg.clone())
|
.with_click_message(click_msg.clone())
|
||||||
// .with_click_message(click_msg.clone())
|
.on_click(move |cx| on_click(cx))
|
||||||
// .on_click(move |cx| on_click(cx))
|
}
|
||||||
// }
|
None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
|
||||||
// None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
|
})
|
||||||
// })
|
})
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext<Self>) {
|
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(
|
fn dismiss_notification_internal(
|
||||||
|
@ -179,33 +177,10 @@ pub mod simple_message_notification {
|
||||||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle,
|
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle,
|
||||||
ViewContext,
|
ViewContext,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use std::sync::Arc;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
|
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 {
|
enum NotificationMessage {
|
||||||
Text(SharedString),
|
Text(SharedString),
|
||||||
Element(fn(TextStyle, &AppContext) -> AnyElement),
|
Element(fn(TextStyle, &AppContext) -> AnyElement),
|
||||||
|
@ -213,7 +188,7 @@ pub mod simple_message_notification {
|
||||||
|
|
||||||
pub struct MessageNotification {
|
pub struct MessageNotification {
|
||||||
message: NotificationMessage,
|
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>,
|
click_message: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +227,7 @@ pub mod simple_message_notification {
|
||||||
|
|
||||||
pub fn on_click<F>(mut self, on_click: F) -> Self
|
pub fn on_click<F>(mut self, on_click: F) -> Self
|
||||||
where
|
where
|
||||||
F: 'static + Send + Sync + Fn(&mut ViewContext<Self>),
|
F: 'static + Fn(&mut ViewContext<Self>),
|
||||||
{
|
{
|
||||||
self.on_click = Some(Arc::new(on_click));
|
self.on_click = Some(Arc::new(on_click));
|
||||||
self
|
self
|
||||||
|
|
|
@ -6,7 +6,7 @@ use gpui::{
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::{h_stack, Button, Icon, IconButton};
|
use ui::{h_stack, Icon, IconButton};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
pub trait StatusItemView: Render {
|
pub trait StatusItemView: Render {
|
||||||
|
@ -53,39 +53,11 @@ impl Render for StatusBar {
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.child(
|
.child(
|
||||||
h_stack().gap_1().child(
|
h_stack().gap_1().child(
|
||||||
// TODO: Language picker
|
// Feedback Tool
|
||||||
div()
|
div()
|
||||||
.border()
|
.border()
|
||||||
.border_color(gpui::red())
|
.border_color(gpui::red())
|
||||||
.child(Button::new("status_buffer_language", "Rust")),
|
.child(IconButton::new("status-feedback", Icon::Envelope)),
|
||||||
),
|
|
||||||
)
|
|
||||||
.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(
|
.child(
|
||||||
|
|
|
@ -30,7 +30,7 @@ command_palette = { package="command_palette2", path = "../command_palette2" }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { package = "client2", path = "../client2" }
|
||||||
# clock = { path = "../clock" }
|
# clock = { path = "../clock" }
|
||||||
copilot = { package = "copilot2", path = "../copilot2" }
|
copilot = { package = "copilot2", path = "../copilot2" }
|
||||||
# copilot_button = { path = "../copilot_button" }
|
copilot_button = { package = "copilot_button2", path = "../copilot_button2" }
|
||||||
diagnostics = { package = "diagnostics2", path = "../diagnostics2" }
|
diagnostics = { package = "diagnostics2", path = "../diagnostics2" }
|
||||||
db = { package = "db2", path = "../db2" }
|
db = { package = "db2", path = "../db2" }
|
||||||
editor = { package="editor2", path = "../editor2" }
|
editor = { package="editor2", path = "../editor2" }
|
||||||
|
@ -44,7 +44,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
install_cli = { package = "install_cli2", path = "../install_cli2" }
|
install_cli = { package = "install_cli2", path = "../install_cli2" }
|
||||||
journal = { package = "journal2", path = "../journal2" }
|
journal = { package = "journal2", path = "../journal2" }
|
||||||
language = { package = "language2", path = "../language2" }
|
language = { package = "language2", path = "../language2" }
|
||||||
# language_selector = { path = "../language_selector" }
|
language_selector = { package = "language_selector2", path = "../language_selector2" }
|
||||||
lsp = { package = "lsp2", path = "../lsp2" }
|
lsp = { package = "lsp2", path = "../lsp2" }
|
||||||
menu = { package = "menu2", path = "../menu2" }
|
menu = { package = "menu2", path = "../menu2" }
|
||||||
# language_tools = { path = "../language_tools" }
|
# language_tools = { path = "../language_tools" }
|
||||||
|
|
|
@ -216,7 +216,7 @@ fn main() {
|
||||||
terminal_view::init(cx);
|
terminal_view::init(cx);
|
||||||
|
|
||||||
// journal2::init(app_state.clone(), cx);
|
// journal2::init(app_state.clone(), cx);
|
||||||
// language_selector::init(cx);
|
language_selector::init(cx);
|
||||||
theme_selector::init(cx);
|
theme_selector::init(cx);
|
||||||
// activity_indicator::init(cx);
|
// activity_indicator::init(cx);
|
||||||
// language_tools::init(cx);
|
// language_tools::init(cx);
|
||||||
|
|
|
@ -136,14 +136,14 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
// cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
|
// cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
|
||||||
// workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
|
// workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
|
||||||
|
|
||||||
// let copilot =
|
let copilot =
|
||||||
// cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
|
cx.build_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
|
||||||
let diagnostic_summary =
|
let diagnostic_summary =
|
||||||
cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
|
cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
|
||||||
let activity_indicator =
|
let activity_indicator =
|
||||||
activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
|
activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
|
||||||
// let active_buffer_language =
|
let active_buffer_language =
|
||||||
// cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
|
cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
|
||||||
// let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
|
// let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
|
||||||
// let feedback_button = cx.add_view(|_| {
|
// let feedback_button = cx.add_view(|_| {
|
||||||
// feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
|
// feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
|
||||||
|
@ -154,8 +154,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
status_bar.add_left_item(activity_indicator, cx);
|
status_bar.add_left_item(activity_indicator, cx);
|
||||||
|
|
||||||
// status_bar.add_right_item(feedback_button, cx);
|
// status_bar.add_right_item(feedback_button, cx);
|
||||||
// status_bar.add_right_item(copilot, cx);
|
status_bar.add_right_item(copilot, cx);
|
||||||
// status_bar.add_right_item(active_buffer_language, cx);
|
status_bar.add_right_item(active_buffer_language, cx);
|
||||||
// status_bar.add_right_item(vim_mode_indicator, cx);
|
// status_bar.add_right_item(vim_mode_indicator, cx);
|
||||||
status_bar.add_right_item(cursor_position, cx);
|
status_bar.add_right_item(cursor_position, cx);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue