Merge branch 'main' into diagnostics-style-2

This commit is contained in:
Marshall Bowers
2023-12-04 11:56:55 -05:00
54 changed files with 2433 additions and 6417 deletions
Generated
+58 -1
View File
@@ -1095,6 +1095,23 @@ dependencies = [
"workspace",
]
[[package]]
name = "breadcrumbs2"
version = "0.1.0"
dependencies = [
"collections",
"editor2",
"gpui2",
"itertools 0.10.5",
"language2",
"project2",
"search2",
"settings2",
"theme2",
"ui2",
"workspace2",
]
[[package]]
name = "bromberg_sl2"
version = "0.6.0"
@@ -1688,7 +1705,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.29.0"
version = "0.29.1"
dependencies = [
"anyhow",
"async-trait",
@@ -2126,6 +2143,25 @@ dependencies = [
"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]]
name = "core-foundation"
version = "0.9.3"
@@ -4774,6 +4810,24 @@ dependencies = [
"workspace",
]
[[package]]
name = "language_selector2"
version = "0.1.0"
dependencies = [
"anyhow",
"editor2",
"fuzzy2",
"gpui2",
"language2",
"picker2",
"project2",
"settings2",
"theme2",
"ui2",
"util",
"workspace2",
]
[[package]]
name = "language_tools"
version = "0.1.0"
@@ -11726,6 +11780,7 @@ dependencies = [
"audio2",
"auto_update2",
"backtrace",
"breadcrumbs2",
"call2",
"channel2",
"chrono",
@@ -11735,6 +11790,7 @@ dependencies = [
"collections",
"command_palette2",
"copilot2",
"copilot_button2",
"ctor",
"db2",
"diagnostics2",
@@ -11754,6 +11810,7 @@ dependencies = [
"isahc",
"journal2",
"language2",
"language_selector2",
"lazy_static",
"libc",
"log",
+2
View File
@@ -9,6 +9,7 @@ members = [
"crates/auto_update",
"crates/auto_update2",
"crates/breadcrumbs",
"crates/breadcrumbs2",
"crates/call",
"crates/call2",
"crates/channel",
@@ -60,6 +61,7 @@ members = [
"crates/language",
"crates/language2",
"crates/language_selector",
"crates/language_selector2",
"crates/language_tools",
"crates/live_kit_client",
"crates/live_kit_server",
+1 -1
View File
@@ -102,7 +102,7 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
})
.detach();
if let Some(version) = *ZED_APP_VERSION {
if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
let auto_updater = cx.build_model(|cx| {
let updater = AutoUpdater::new(version, http_client, server_url);
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "breadcrumbs2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/breadcrumbs.rs"
doctest = false
[dependencies]
collections = { path = "../collections" }
editor = { package = "editor2", path = "../editor2" }
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
language = { package = "language2", path = "../language2" }
project = { package = "project2", path = "../project2" }
search = { package = "search2", path = "../search2" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
workspace = { package = "workspace2", path = "../workspace2" }
# outline = { path = "../outline" }
itertools = "0.10"
[dev-dependencies]
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+204
View File
@@ -0,0 +1,204 @@
use gpui::{
Component, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
ViewContext, WeakView,
};
use itertools::Itertools;
use theme::ActiveTheme;
use ui::{ButtonCommon, ButtonLike, ButtonStyle, Clickable, Disableable, Label};
use workspace::{
item::{ItemEvent, ItemHandle},
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
};
pub enum Event {
UpdateLocation,
}
pub struct Breadcrumbs {
pane_focused: bool,
active_item: Option<Box<dyn ItemHandle>>,
subscription: Option<Subscription>,
_workspace: WeakView<Workspace>,
}
impl Breadcrumbs {
pub fn new(workspace: &Workspace) -> Self {
Self {
pane_focused: false,
active_item: Default::default(),
subscription: Default::default(),
_workspace: workspace.weak_handle(),
}
}
}
impl EventEmitter<Event> for Breadcrumbs {}
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs {
type Element = Component<ButtonLike>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let button = ButtonLike::new("breadcrumbs")
.style(ButtonStyle::Transparent)
.disabled(true);
let active_item = match &self.active_item {
Some(active_item) => active_item,
None => return button.into_element(),
};
let not_editor = active_item.downcast::<editor::Editor>().is_none();
let breadcrumbs = match active_item.breadcrumbs(cx.theme(), cx) {
Some(breadcrumbs) => breadcrumbs,
None => return button.into_element(),
}
.into_iter()
.map(|breadcrumb| {
StyledText::new(breadcrumb.text)
.with_highlights(&cx.text_style(), breadcrumb.highlights.unwrap_or_default())
.into_any()
});
let button = button.children(Itertools::intersperse_with(breadcrumbs, || {
Label::new(" ").into_any_element()
}));
if not_editor || !self.pane_focused {
return button.into_element();
}
// let this = cx.view().downgrade();
button
.style(ButtonStyle::Filled)
.disabled(false)
.on_click(move |_, _cx| {
todo!("outline::toggle");
// this.update(cx, |this, cx| {
// if let Some(workspace) = this.workspace.upgrade() {
// workspace.update(cx, |_workspace, _cx| {
// outline::toggle(workspace, &Default::default(), cx)
// })
// }
// })
// .ok();
})
.into_element()
}
}
// impl View for Breadcrumbs {
// fn ui_name() -> &'static str {
// "Breadcrumbs"
// }
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
// let active_item = match &self.active_item {
// Some(active_item) => active_item,
// None => return Empty::new().into_any(),
// };
// let not_editor = active_item.downcast::<editor::Editor>().is_none();
// let theme = theme::current(cx).clone();
// let style = &theme.workspace.toolbar.breadcrumbs;
// let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
// Some(breadcrumbs) => breadcrumbs,
// None => return Empty::new().into_any(),
// }
// .into_iter()
// .map(|breadcrumb| {
// Text::new(
// breadcrumb.text,
// theme.workspace.toolbar.breadcrumbs.default.text.clone(),
// )
// .with_highlights(breadcrumb.highlights.unwrap_or_default())
// .into_any()
// });
// let crumbs = Flex::row()
// .with_children(Itertools::intersperse_with(breadcrumbs, || {
// Label::new(" ", style.default.text.clone()).into_any()
// }))
// .constrained()
// .with_height(theme.workspace.toolbar.breadcrumb_height)
// .contained();
// if not_editor || !self.pane_focused {
// return crumbs
// .with_style(style.default.container)
// .aligned()
// .left()
// .into_any();
// }
// MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
// let style = style.style_for(state);
// crumbs.with_style(style.container)
// })
// .on_click(MouseButton::Left, |_, this, cx| {
// if let Some(workspace) = this.workspace.upgrade(cx) {
// workspace.update(cx, |workspace, cx| {
// outline::toggle(workspace, &Default::default(), cx)
// })
// }
// })
// .with_tooltip::<Breadcrumbs>(
// 0,
// "Show symbol outline".to_owned(),
// Some(Box::new(outline::Toggle)),
// theme.tooltip.clone(),
// cx,
// )
// .aligned()
// .left()
// .into_any()
// }
// }
impl ToolbarItemView for Breadcrumbs {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) -> ToolbarItemLocation {
cx.notify();
self.active_item = None;
if let Some(item) = active_pane_item {
let this = cx.view().downgrade();
self.subscription = Some(item.subscribe_to_item_events(
cx,
Box::new(move |event, cx| {
if let ItemEvent::UpdateBreadcrumbs = event {
this.update(cx, |_, cx| {
cx.emit(Event::UpdateLocation);
cx.notify();
})
.ok();
}
}),
));
self.active_item = Some(item.boxed_clone());
item.breadcrumb_location(cx)
} else {
ToolbarItemLocation::Hidden
}
}
// fn location_for_event(
// &self,
// _: &Event,
// current_location: ToolbarItemLocation,
// cx: &AppContext,
// ) -> ToolbarItemLocation {
// if let Some(active_item) = self.active_item.as_ref() {
// active_item.breadcrumb_location(cx)
// } else {
// current_location
// }
// }
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
self.pane_focused = pane_focused;
}
}
+1 -1
View File
@@ -346,7 +346,7 @@ impl<T: Entity> Drop for PendingEntitySubscription<T> {
}
}
#[derive(Copy, Clone)]
#[derive(Debug, Copy, Clone)]
pub struct TelemetrySettings {
pub diagnostics: bool,
pub metrics: bool,
+1 -1
View File
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.29.0"
version = "0.29.1"
publish = false
[[bin]]
+7
View File
@@ -1220,6 +1220,13 @@ impl Database {
self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
.await?;
if new_parent
.ancestors_including_self()
.any(|id| id == channel.id)
{
Err(anyhow!("cannot move a channel into one of its descendants"))?;
}
new_parent_path = new_parent.path();
new_parent_channel = Some(new_parent);
} else {
@@ -450,6 +450,20 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
(livestreaming_id, &[projects_id]),
],
);
// Can't move a channel into its ancestor
db.move_channel(projects_id, Some(livestreaming_id), user_id)
.await
.unwrap_err();
let result = db.get_channels_for_user(user_id).await.unwrap();
assert_channel_tree(
result.channels,
&[
(zed_id, &[]),
(projects_id, &[]),
(livestreaming_id, &[projects_id]),
],
);
}
test_both_dbs!(
@@ -1220,6 +1220,13 @@ impl Database {
self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
.await?;
if new_parent
.ancestors_including_self()
.any(|id| id == channel.id)
{
Err(anyhow!("cannot move a channel into one of its descendants"))?;
}
new_parent_path = new_parent.path();
new_parent_channel = Some(new_parent);
} else {
+14 -2
View File
@@ -420,8 +420,6 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
.await
.unwrap();
// Dag is: zed - projects - livestreaming
// Move to same parent should be a no-op
assert!(db
.move_channel(projects_id, Some(zed_id), user_id)
@@ -450,6 +448,20 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
(livestreaming_id, &[projects_id]),
],
);
// Can't move a channel into its ancestor
db.move_channel(projects_id, Some(livestreaming_id), user_id)
.await
.unwrap_err();
let result = db.get_channels_for_user(user_id).await.unwrap();
assert_channel_tree(
result.channels,
&[
(zed_id, &[]),
(projects_id, &[]),
(livestreaming_id, &[projects_id]),
],
);
}
test_both_dbs!(
+34 -36
View File
@@ -1,5 +1,5 @@
#![allow(unused)]
// mod channel_modal;
mod channel_modal;
mod contact_finder;
// use crate::{
@@ -192,6 +192,8 @@ use workspace::{
use crate::{face_pile::FacePile, CollaborationPanelSettings};
use self::channel_modal::ChannelModal;
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, _| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
@@ -2058,13 +2060,11 @@ impl CollabPanel {
}
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>) {
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>) {
@@ -2156,38 +2156,36 @@ impl CollabPanel {
})
}
// fn show_channel_modal(
// &mut self,
// channel_id: ChannelId,
// mode: channel_modal::Mode,
// cx: &mut ViewContext<Self>,
// ) {
// let workspace = self.workspace.clone();
// let user_store = self.user_store.clone();
// let channel_store = self.channel_store.clone();
// let members = self.channel_store.update(cx, |channel_store, cx| {
// channel_store.get_channel_member_details(channel_id, cx)
// });
fn show_channel_modal(
&mut self,
channel_id: ChannelId,
mode: channel_modal::Mode,
cx: &mut ViewContext<Self>,
) {
let workspace = self.workspace.clone();
let user_store = self.user_store.clone();
let channel_store = self.channel_store.clone();
let members = self.channel_store.update(cx, |channel_store, cx| {
channel_store.get_channel_member_details(channel_id, cx)
});
// cx.spawn(|_, mut cx| async move {
// let members = members.await?;
// workspace.update(&mut cx, |workspace, cx| {
// workspace.toggle_modal(cx, |_, cx| {
// cx.add_view(|cx| {
// ChannelModal::new(
// user_store.clone(),
// channel_store.clone(),
// channel_id,
// mode,
// members,
// cx,
// )
// })
// });
// })
// })
// .detach();
// }
cx.spawn(|_, mut cx| async move {
let members = members.await?;
workspace.update(&mut cx, |workspace, cx| {
workspace.toggle_modal(cx, |cx| {
ChannelModal::new(
user_store.clone(),
channel_store.clone(),
channel_id,
mode,
members,
cx,
)
});
})
})
.detach();
}
// fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext<Self>) {
// self.remove_channel(action.channel_id, cx)
@@ -3,58 +3,54 @@ use client::{
proto::{self, ChannelRole, ChannelVisibility},
User, UserId, UserStore,
};
use context_menu::{ContextMenu, ContextMenuItem};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions,
elements::*,
platform::{CursorStyle, MouseButton},
AppContext, ClipboardItem, Entity, ModelHandle, MouseState, Task, View, ViewContext,
ViewHandle,
actions, div, AppContext, ClipboardItem, DismissEvent, Div, Entity, EventEmitter,
FocusableView, Model, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext,
WeakView,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use ui::v_stack;
use util::TryFutureExt;
use workspace::Modal;
actions!(
channel_modal,
[
SelectNextControl,
ToggleMode,
ToggleMemberAdmin,
RemoveMember
]
SelectNextControl,
ToggleMode,
ToggleMemberAdmin,
RemoveMember
);
pub fn init(cx: &mut AppContext) {
Picker::<ChannelModalDelegate>::init(cx);
cx.add_action(ChannelModal::toggle_mode);
cx.add_action(ChannelModal::toggle_member_admin);
cx.add_action(ChannelModal::remove_member);
cx.add_action(ChannelModal::dismiss);
}
// pub fn init(cx: &mut AppContext) {
// Picker::<ChannelModalDelegate>::init(cx);
// cx.add_action(ChannelModal::toggle_mode);
// cx.add_action(ChannelModal::toggle_member_admin);
// cx.add_action(ChannelModal::remove_member);
// cx.add_action(ChannelModal::dismiss);
// }
pub struct ChannelModal {
picker: ViewHandle<Picker<ChannelModalDelegate>>,
channel_store: ModelHandle<ChannelStore>,
picker: View<Picker<ChannelModalDelegate>>,
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
has_focus: bool,
}
impl ChannelModal {
pub fn new(
user_store: ModelHandle<UserStore>,
channel_store: ModelHandle<ChannelStore>,
user_store: Model<UserStore>,
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
mode: Mode,
members: Vec<ChannelMembership>,
cx: &mut ViewContext<Self>,
) -> Self {
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(
ChannelModalDelegate {
channel_modal,
matching_users: Vec::new(),
matching_member_indices: Vec::new(),
selected_index: 0,
@@ -64,20 +60,17 @@ impl ChannelModal {
match_candidates: Vec::new(),
members,
mode,
context_menu: cx.add_view(|cx| {
let mut menu = ContextMenu::new(cx.view_id(), cx);
menu.set_position_mode(OverlayPositionMode::Local);
menu
}),
// context_menu: cx.add_view(|cx| {
// let mut menu = ContextMenu::new(cx.view_id(), cx);
// menu.set_position_mode(OverlayPositionMode::Local);
// menu
// }),
},
cx,
)
.with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
});
cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
let has_focus = picker.read(cx).has_focus();
let has_focus = picker.focus_handle(cx).contains_focused(cx);
Self {
picker,
@@ -88,7 +81,7 @@ impl ChannelModal {
}
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::InviteMembers => Mode::ManageMembers,
};
@@ -103,20 +96,20 @@ impl ChannelModal {
let mut members = channel_store
.update(&mut cx, |channel_store, cx| {
channel_store.get_channel_member_details(channel_id, cx)
})
})?
.await?;
members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
this.update(&mut cx, |this, cx| {
this.picker
.update(cx, |picker, _| picker.delegate_mut().members = members);
.update(cx, |picker, _| picker.delegate.members = members);
})?;
}
this.update(&mut cx, |this, cx| {
this.picker.update(cx, |picker, cx| {
let delegate = picker.delegate_mut();
let delegate = &mut picker.delegate;
delegate.mode = mode;
delegate.selected_index = 0;
picker.set_query("", cx);
@@ -131,203 +124,194 @@ impl ChannelModal {
fn toggle_member_admin(&mut self, _: &ToggleMemberAdmin, cx: &mut ViewContext<Self>) {
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>) {
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>) {
cx.emit(PickerEvent::Dismiss);
cx.emit(DismissEvent);
}
}
impl Entity for ChannelModal {
type Event = PickerEvent;
}
impl EventEmitter<DismissEvent> for ChannelModal {}
impl View for ChannelModal {
fn ui_name() -> &'static str {
"ChannelModal"
}
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 FocusableView for ChannelModal {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Modal for ChannelModal {
fn has_focus(&self) -> bool {
self.has_focus
impl Render for ChannelModal {
type Element = Div;
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 {
match event {
PickerEvent::Dismiss => true,
}
}
// 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;
// }
}
#[derive(Copy, Clone, PartialEq)]
@@ -337,19 +321,22 @@ pub enum Mode {
}
pub struct ChannelModalDelegate {
channel_modal: WeakView<ChannelModal>,
matching_users: Vec<Arc<User>>,
matching_member_indices: Vec<usize>,
user_store: ModelHandle<UserStore>,
channel_store: ModelHandle<ChannelStore>,
user_store: Model<UserStore>,
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
selected_index: usize,
mode: Mode,
match_candidates: Vec<StringMatchCandidate>,
members: Vec<ChannelMembership>,
context_menu: ViewHandle<ContextMenu>,
// context_menu: ViewHandle<ContextMenu>,
}
impl PickerDelegate for ChannelModalDelegate {
type ListItem = Div;
fn placeholder_text(&self) -> Arc<str> {
"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,
&query,
true,
usize::MAX,
&Default::default(),
cx.background().clone(),
cx.background_executor().clone(),
));
cx.spawn(|picker, mut cx| async move {
picker
.update(&mut cx, |picker, cx| {
let delegate = picker.delegate_mut();
let delegate = &mut picker.delegate;
delegate.matching_member_indices.clear();
delegate
.matching_member_indices
@@ -412,8 +399,7 @@ impl PickerDelegate for ChannelModalDelegate {
async {
let users = search_users.await?;
picker.update(&mut cx, |picker, cx| {
let delegate = picker.delegate_mut();
delegate.matching_users = users;
picker.delegate.matching_users = users;
cx.notify();
})?;
anyhow::Ok(())
@@ -445,138 +431,142 @@ impl PickerDelegate for ChannelModalDelegate {
}
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(
&self,
ix: usize,
mouse_state: &mut MouseState,
selected: bool,
cx: &gpui::AppContext,
) -> AnyElement<Picker<Self>> {
let full_theme = &theme::current(cx);
let theme = &full_theme.collab_panel.channel_modal;
let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
let (user, role) = self.user_at_index(ix).unwrap();
let request_status = self.member_status(user.id, cx);
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
None
// let full_theme = &theme::current(cx);
// let theme = &full_theme.collab_panel.channel_modal;
// let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
// let (user, role) = self.user_at_index(ix).unwrap();
// let request_status = self.member_status(user.id, cx);
let style = tabbed_modal
.picker
.item
.in_state(selected)
.style_for(mouse_state);
// let style = tabbed_modal
// .picker
// .item
// .in_state(selected)
// .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()
.with_children(user.avatar.clone().map(|avatar| {
Image::from_data(avatar)
.with_style(theme.contact_avatar)
.aligned()
.left()
}))
.with_child(
Label::new(user.github_login.clone(), style.label.clone())
.contained()
.with_style(theme.contact_username)
.aligned()
.left(),
)
.with_children({
(in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then(
|| {
Label::new("Invited", theme.member_tag.text.clone())
.contained()
.with_style(theme.member_tag.container)
.aligned()
.left()
},
)
})
.with_children(if in_manage && role == Some(ChannelRole::Admin) {
Some(
Label::new("Admin", theme.member_tag.text.clone())
.contained()
.with_style(theme.member_tag.container)
.aligned()
.left(),
)
} else if in_manage && role == Some(ChannelRole::Guest) {
Some(
Label::new("Guest", theme.member_tag.text.clone())
.contained()
.with_style(theme.member_tag.container)
.aligned()
.left(),
)
} else {
None
})
.with_children({
let svg = match self.mode {
Mode::ManageMembers => Some(
Svg::new("icons/ellipsis.svg")
.with_color(theme.member_icon.color)
.constrained()
.with_width(theme.member_icon.icon_width)
.aligned()
.constrained()
.with_width(theme.member_icon.button_width)
.with_height(theme.member_icon.button_width)
.contained()
.with_style(theme.member_icon.container),
),
Mode::InviteMembers => match request_status {
Some(proto::channel_member::Kind::Member) => Some(
Svg::new("icons/check.svg")
.with_color(theme.member_icon.color)
.constrained()
.with_width(theme.member_icon.icon_width)
.aligned()
.constrained()
.with_width(theme.member_icon.button_width)
.with_height(theme.member_icon.button_width)
.contained()
.with_style(theme.member_icon.container),
),
Some(proto::channel_member::Kind::Invitee) => Some(
Svg::new("icons/check.svg")
.with_color(theme.invitee_icon.color)
.constrained()
.with_width(theme.invitee_icon.icon_width)
.aligned()
.constrained()
.with_width(theme.invitee_icon.button_width)
.with_height(theme.invitee_icon.button_width)
.contained()
.with_style(theme.invitee_icon.container),
),
Some(proto::channel_member::Kind::AncestorMember) | None => None,
},
};
// let mut result = Flex::row()
// .with_children(user.avatar.clone().map(|avatar| {
// Image::from_data(avatar)
// .with_style(theme.contact_avatar)
// .aligned()
// .left()
// }))
// .with_child(
// Label::new(user.github_login.clone(), style.label.clone())
// .contained()
// .with_style(theme.contact_username)
// .aligned()
// .left(),
// )
// .with_children({
// (in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then(
// || {
// Label::new("Invited", theme.member_tag.text.clone())
// .contained()
// .with_style(theme.member_tag.container)
// .aligned()
// .left()
// },
// )
// })
// .with_children(if in_manage && role == Some(ChannelRole::Admin) {
// Some(
// Label::new("Admin", theme.member_tag.text.clone())
// .contained()
// .with_style(theme.member_tag.container)
// .aligned()
// .left(),
// )
// } else if in_manage && role == Some(ChannelRole::Guest) {
// Some(
// Label::new("Guest", theme.member_tag.text.clone())
// .contained()
// .with_style(theme.member_tag.container)
// .aligned()
// .left(),
// )
// } else {
// None
// })
// .with_children({
// let svg = match self.mode {
// Mode::ManageMembers => Some(
// Svg::new("icons/ellipsis.svg")
// .with_color(theme.member_icon.color)
// .constrained()
// .with_width(theme.member_icon.icon_width)
// .aligned()
// .constrained()
// .with_width(theme.member_icon.button_width)
// .with_height(theme.member_icon.button_width)
// .contained()
// .with_style(theme.member_icon.container),
// ),
// Mode::InviteMembers => match request_status {
// Some(proto::channel_member::Kind::Member) => Some(
// Svg::new("icons/check.svg")
// .with_color(theme.member_icon.color)
// .constrained()
// .with_width(theme.member_icon.icon_width)
// .aligned()
// .constrained()
// .with_width(theme.member_icon.button_width)
// .with_height(theme.member_icon.button_width)
// .contained()
// .with_style(theme.member_icon.container),
// ),
// Some(proto::channel_member::Kind::Invitee) => Some(
// Svg::new("icons/check.svg")
// .with_color(theme.invitee_icon.color)
// .constrained()
// .with_width(theme.invitee_icon.icon_width)
// .aligned()
// .constrained()
// .with_width(theme.invitee_icon.button_width)
// .with_height(theme.invitee_icon.button_width)
// .contained()
// .with_style(theme.invitee_icon.container),
// ),
// Some(proto::channel_member::Kind::AncestorMember) | None => None,
// },
// };
svg.map(|svg| svg.aligned().flex_float().into_any())
})
.contained()
.with_style(style.container)
.constrained()
.with_height(tabbed_modal.row_height)
.into_any();
// svg.map(|svg| svg.aligned().flex_float().into_any())
// })
// .contained()
// .with_style(style.container)
// .constrained()
// .with_height(tabbed_modal.row_height)
// .into_any();
if selected {
result = Stack::new()
.with_child(result)
.with_child(
ChildView::new(&self.context_menu, cx)
.aligned()
.top()
.right(),
)
.into_any();
}
// if selected {
// result = Stack::new()
// .with_child(result)
// .with_child(
// ChildView::new(&self.context_menu, cx)
// .aligned()
// .top()
// .right(),
// )
// .into_any();
// }
result
// result
}
}
@@ -623,7 +613,7 @@ impl ChannelModalDelegate {
cx.spawn(|picker, mut cx| async move {
update.await?;
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) {
member.role = new_role;
}
@@ -644,7 +634,7 @@ impl ChannelModalDelegate {
cx.spawn(|picker, mut cx| async move {
update.await?;
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) {
this.members.remove(ix);
this.matching_member_indices.retain_mut(|member_ix| {
@@ -683,7 +673,7 @@ impl ChannelModalDelegate {
kind: proto::channel_member::Kind::Invitee,
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()) {
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>>) {
self.context_menu.update(cx, |context_menu, cx| {
context_menu.show(
Default::default(),
AnchorCorner::TopRight,
vec![
ContextMenuItem::action("Remove", RemoveMember),
ContextMenuItem::action(
if role == ChannelRole::Admin {
"Make non-admin"
} else {
"Make admin"
},
ToggleMemberAdmin,
),
],
cx,
)
})
// self.context_menu.update(cx, |context_menu, cx| {
// context_menu.show(
// Default::default(),
// AnchorCorner::TopRight,
// vec![
// ContextMenuItem::action("Remove", RemoveMember),
// ContextMenuItem::action(
// if role == ChannelRole::Admin {
// "Make non-admin"
// } else {
// "Make admin"
// },
// ToggleMemberAdmin,
// ),
// ],
// cx,
// )
// })
}
}
+27
View 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"] }
@@ -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);
}
}
}
+10 -40
View File
@@ -154,7 +154,6 @@ pub fn render_parsed_markdown(
}
}),
);
let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights);
let mut links = Vec::new();
let mut link_ranges = Vec::new();
@@ -167,7 +166,7 @@ pub fn render_parsed_markdown(
InteractiveText::new(
element_id,
StyledText::new(parsed.text.clone()).with_runs(runs),
StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
)
.on_click(link_ranges, move |clicked_range_ix, cx| {
match &links[clicked_range_ix] {
@@ -1199,11 +1198,7 @@ impl CompletionsMenu {
),
);
let completion_label = StyledText::new(completion.label.text.clone())
.with_runs(text_runs_for_highlights(
&completion.label.text,
&style.text,
highlights,
));
.with_highlights(&style.text, highlights);
let documentation_label =
if let Some(Documentation::SingleLine(text)) = documentation {
Some(SharedString::from(text.clone()))
@@ -1925,14 +1920,14 @@ impl Editor {
// self.buffer.read(cx).read(cx).file_at(point).cloned()
// }
// pub fn active_excerpt(
// &self,
// cx: &AppContext,
// ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
// self.buffer
// .read(cx)
// .excerpt_containing(self.selections.newest_anchor().head(), cx)
// }
pub fn active_excerpt(
&self,
cx: &AppContext,
) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
self.buffer
.read(cx)
.excerpt_containing(self.selections.newest_anchor().head(), cx)
}
// pub fn style(&self, cx: &AppContext) -> EditorStyle {
// build_style(
@@ -9777,31 +9772,6 @@ pub fn diagnostic_style(
}
}
pub fn text_runs_for_highlights(
text: &str,
default_style: &TextStyle,
highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
) -> Vec<TextRun> {
let mut runs = Vec::new();
let mut ix = 0;
for (range, highlight) in highlights {
if ix < range.start {
runs.push(default_style.clone().to_run(range.start - ix));
}
runs.push(
default_style
.clone()
.highlight(highlight)
.to_run(range.len()),
);
ix = range.end;
}
if ix < text.len() {
runs.push(default_style.to_run(text.len() - ix));
}
runs
}
pub fn styled_runs_for_code_label<'a>(
label: &'a CodeLabel,
syntax_theme: &'a theme::SyntaxTheme,
+4 -4
View File
@@ -992,10 +992,6 @@ impl Interactivity {
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
}
let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
&& pending_mouse_down.borrow().is_none();
if !is_hovered {
@@ -1003,6 +999,10 @@ impl Interactivity {
return;
}
if phase != DispatchPhase::Bubble {
return;
}
if active_tooltip.borrow().is_none() {
let task = cx.spawn({
let active_tooltip = active_tooltip.clone();
+25 -3
View File
@@ -1,6 +1,7 @@
use crate::{
Bounds, DispatchPhase, Element, ElementId, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent,
Pixels, Point, SharedString, Size, TextRun, WhiteSpace, WindowContext, WrappedLine,
Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
WhiteSpace, WindowContext, WrappedLine,
};
use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard};
@@ -87,7 +88,28 @@ impl StyledText {
}
}
pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
pub fn with_highlights(
mut self,
default_style: &TextStyle,
highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
) -> Self {
let mut runs = Vec::new();
let mut ix = 0;
for (range, highlight) in highlights {
if ix < range.start {
runs.push(default_style.clone().to_run(range.start - ix));
}
runs.push(
default_style
.clone()
.highlight(highlight)
.to_run(range.len()),
);
ix = range.end;
}
if ix < self.text.len() {
runs.push(default_style.to_run(self.text.len() - ix));
}
self.runs = Some(runs);
self
}
+17 -3
View File
@@ -472,13 +472,27 @@ pub enum PromptLevel {
Critical,
}
/// The style of the cursor (pointer)
#[derive(Copy, Clone, Debug)]
pub enum CursorStyle {
Arrow,
ResizeLeftRight,
ResizeUpDown,
PointingHand,
IBeam,
Crosshair,
ClosedHand,
OpenHand,
PointingHand,
ResizeLeft,
ResizeRight,
ResizeLeftRight,
ResizeUp,
ResizeDown,
ResizeUpDown,
DisappearingItem,
IBeamCursorForVerticalLayout,
OperationNotAllowed,
DragLink,
DragCopy,
ContextualMenu,
}
impl Default for CursorStyle {
+24 -5
View File
@@ -724,16 +724,35 @@ impl Platform for MacPlatform {
}
}
/// Match cursor style to one of the styles available
/// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
fn set_cursor_style(&self, style: CursorStyle) {
unsafe {
let new_cursor: id = match style {
CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
CursorStyle::ResizeLeftRight => {
msg_send![class!(NSCursor), resizeLeftRightCursor]
}
CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
CursorStyle::Crosshair => msg_send![class!(NSCursor), crosshairCursor],
CursorStyle::ClosedHand => msg_send![class!(NSCursor), closedHandCursor],
CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor],
CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor],
CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
CursorStyle::DisappearingItem => {
msg_send![class!(NSCursor), disappearingItemCursor]
}
CursorStyle::IBeamCursorForVerticalLayout => {
msg_send![class!(NSCursor), IBeamCursorForVerticalLayout]
}
CursorStyle::OperationNotAllowed => {
msg_send![class!(NSCursor), operationNotAllowedCursor]
}
CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
};
let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
+119
View File
@@ -101,6 +101,125 @@ pub trait Styled: Sized {
self
}
/// Sets cursor style when hovering over an element to `text`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_text(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::IBeam);
self
}
/// Sets cursor style when hovering over an element to `move`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_move(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ClosedHand);
self
}
/// Sets cursor style when hovering over an element to `not-allowed`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_not_allowed(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed);
self
}
/// Sets cursor style when hovering over an element to `context-menu`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_context_menu(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ContextualMenu);
self
}
/// Sets cursor style when hovering over an element to `crosshair`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_crosshair(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::Crosshair);
self
}
/// Sets cursor style when hovering over an element to `vertical-text`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_vertical_text(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::IBeamCursorForVerticalLayout);
self
}
/// Sets cursor style when hovering over an element to `alias`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_alias(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::DragLink);
self
}
/// Sets cursor style when hovering over an element to `copy`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_copy(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::DragCopy);
self
}
/// Sets cursor style when hovering over an element to `no-drop`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_no_drop(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed);
self
}
/// Sets cursor style when hovering over an element to `grab`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_grab(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::OpenHand);
self
}
/// Sets cursor style when hovering over an element to `grabbing`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_grabbing(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ClosedHand);
self
}
/// Sets cursor style when hovering over an element to `col-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_col_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeLeftRight);
self
}
/// Sets cursor style when hovering over an element to `row-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_row_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeUpDown);
self
}
/// Sets cursor style when hovering over an element to `n-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_n_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeUp);
self
}
/// Sets cursor style when hovering over an element to `e-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_e_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeRight);
self
}
/// Sets cursor style when hovering over an element to `s-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_s_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeDown);
self
}
/// Sets cursor style when hovering over an element to `w-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_w_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeLeft);
self
}
/// Sets the whitespace of the element to `normal`.
/// [Docs](https://tailwindcss.com/docs/whitespace#normal)
fn whitespace_normal(mut self) -> Self {
+26
View 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"] }
@@ -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();
}
}
@@ -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())),
)
}
}
+9
View File
@@ -178,6 +178,15 @@ impl<D: PickerDelegate> Picker<D> {
}
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> {
+3 -2
View File
@@ -1661,14 +1661,15 @@ impl Project {
path: impl Into<ProjectPath>,
cx: &mut ModelContext<Self>,
) -> Task<Result<(ProjectEntryId, AnyModelHandle)>> {
let task = self.open_buffer(path, cx);
let project_path = path.into();
let task = self.open_buffer(project_path.clone(), cx);
cx.spawn_weak(|_, cx| async move {
let buffer = task.await?;
let project_entry_id = buffer
.read_with(&cx, |buffer, cx| {
File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
})
.ok_or_else(|| anyhow!("no project entry"))?;
.with_context(|| format!("no project entry for {project_path:?}"))?;
let buffer: &AnyModelHandle = &buffer;
Ok((project_entry_id, buffer.clone()))
+3 -2
View File
@@ -1691,14 +1691,15 @@ impl Project {
path: impl Into<ProjectPath>,
cx: &mut ModelContext<Self>,
) -> Task<Result<(ProjectEntryId, AnyModel)>> {
let task = self.open_buffer(path, cx);
let project_path = path.into();
let task = self.open_buffer(project_path.clone(), cx);
cx.spawn(move |_, mut cx| async move {
let buffer = task.await?;
let project_entry_id = buffer
.update(&mut cx, |buffer, cx| {
File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
})?
.ok_or_else(|| anyhow!("no project entry"))?;
.with_context(|| format!("no project entry for {project_path:?}"))?;
let buffer: &AnyModel = &buffer;
Ok((project_entry_id, buffer.clone()))
+1 -1
View File
@@ -9,4 +9,4 @@ pub use notification::*;
pub use peer::*;
mod macros;
pub const PROTOCOL_VERSION: u32 = 64;
pub const PROTOCOL_VERSION: u32 = 66;
+2
View File
@@ -1,4 +1,5 @@
mod auto_height_editor;
mod cursor;
mod focus;
mod kitchen_sink;
mod picker;
@@ -7,6 +8,7 @@ mod text;
mod z_index;
pub use auto_height_editor::*;
pub use cursor::*;
pub use focus::*;
pub use kitchen_sink::*;
pub use picker::*;
+112
View File
@@ -0,0 +1,112 @@
use gpui::{Div, Render, Stateful};
use story::Story;
use ui::prelude::*;
pub struct CursorStory;
impl Render for CursorStory {
type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
let all_cursors: [(&str, Box<dyn Fn(Stateful<Div>) -> Stateful<Div>>); 19] = [
(
"cursor_default",
Box::new(|el: Stateful<Div>| el.cursor_default()),
),
(
"cursor_pointer",
Box::new(|el: Stateful<Div>| el.cursor_pointer()),
),
(
"cursor_text",
Box::new(|el: Stateful<Div>| el.cursor_text()),
),
(
"cursor_move",
Box::new(|el: Stateful<Div>| el.cursor_move()),
),
(
"cursor_not_allowed",
Box::new(|el: Stateful<Div>| el.cursor_not_allowed()),
),
(
"cursor_context_menu",
Box::new(|el: Stateful<Div>| el.cursor_context_menu()),
),
(
"cursor_crosshair",
Box::new(|el: Stateful<Div>| el.cursor_crosshair()),
),
(
"cursor_vertical_text",
Box::new(|el: Stateful<Div>| el.cursor_vertical_text()),
),
(
"cursor_alias",
Box::new(|el: Stateful<Div>| el.cursor_alias()),
),
(
"cursor_copy",
Box::new(|el: Stateful<Div>| el.cursor_copy()),
),
(
"cursor_no_drop",
Box::new(|el: Stateful<Div>| el.cursor_no_drop()),
),
(
"cursor_grab",
Box::new(|el: Stateful<Div>| el.cursor_grab()),
),
(
"cursor_grabbing",
Box::new(|el: Stateful<Div>| el.cursor_grabbing()),
),
(
"cursor_col_resize",
Box::new(|el: Stateful<Div>| el.cursor_col_resize()),
),
(
"cursor_row_resize",
Box::new(|el: Stateful<Div>| el.cursor_row_resize()),
),
(
"cursor_n_resize",
Box::new(|el: Stateful<Div>| el.cursor_n_resize()),
),
(
"cursor_e_resize",
Box::new(|el: Stateful<Div>| el.cursor_e_resize()),
),
(
"cursor_s_resize",
Box::new(|el: Stateful<Div>| el.cursor_s_resize()),
),
(
"cursor_w_resize",
Box::new(|el: Stateful<Div>| el.cursor_w_resize()),
),
];
Story::container()
.flex()
.gap_1()
.child(Story::title("cursor"))
.children(all_cursors.map(|(name, apply_cursor)| {
div().gap_1().flex().text_color(gpui::white()).child(
div()
.flex()
.items_center()
.justify_center()
.id(name)
.map(apply_cursor)
.w_64()
.h_8()
.bg(gpui::red())
.hover(|style| style.bg(gpui::blue()))
.active(|style| style.bg(gpui::green()))
.text_sm()
.child(Story::label(name)),
)
}))
}
}
+6 -8
View File
@@ -1,6 +1,6 @@
use gpui::{
blue, div, green, red, white, Div, InteractiveText, ParentElement, Render, Styled, StyledText,
TextRun, View, VisualContext, WindowContext,
blue, div, green, red, white, Div, HighlightStyle, InteractiveText, ParentElement, Render,
Styled, StyledText, View, VisualContext, WindowContext,
};
use ui::v_stack;
@@ -59,13 +59,11 @@ impl Render for TextStory {
))).child(
InteractiveText::new(
"interactive",
StyledText::new("Hello world, how is it going?").with_runs(vec![
cx.text_style().to_run(6),
TextRun {
StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
(6..11, HighlightStyle {
background_color: Some(green()),
..cx.text_style().to_run(5)
},
cx.text_style().to_run(18),
..Default::default()
}),
]),
)
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
+2
View File
@@ -17,6 +17,7 @@ pub enum ComponentStory {
Button,
Checkbox,
ContextMenu,
Cursor,
Disclosure,
Focus,
Icon,
@@ -40,6 +41,7 @@ impl ComponentStory {
Self::Button => cx.build_view(|_| ui::ButtonStory).into(),
Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
Self::Cursor => cx.build_view(|_| crate::stories::CursorStory).into(),
Self::Disclosure => cx.build_view(|_| ui::DisclosureStory).into(),
Self::Focus => FocusStory::view(cx).into(),
Self::Icon => cx.build_view(|_| ui::IconStory).into(),
+2 -2
View File
@@ -52,13 +52,13 @@ pub(crate) fn one_dark() -> Theme {
element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
element_disabled: SystemColors::default().transparent,
drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0),
ghost_element_background: SystemColors::default().transparent,
ghost_element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
ghost_element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
ghost_element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
ghost_element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
ghost_element_disabled: SystemColors::default().transparent,
text: hsla(221. / 360., 11. / 100., 86. / 100., 1.0),
text_muted: hsla(218.0 / 360., 7. / 100., 46. / 100., 1.0),
text_placeholder: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
+1
View File
@@ -1,4 +1,5 @@
mod button;
pub(self) mod button_icon;
mod button_like;
mod icon_button;
+64 -8
View File
@@ -1,13 +1,22 @@
use gpui::{AnyView, DefiniteLength};
use crate::prelude::*;
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Label, LineHeightStyle};
use crate::{
ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle,
};
use super::button_icon::ButtonIcon;
#[derive(IntoElement)]
pub struct Button {
base: ButtonLike,
label: SharedString,
label_color: Option<Color>,
selected_label: Option<SharedString>,
icon: Option<Icon>,
icon_size: Option<IconSize>,
icon_color: Option<Color>,
selected_icon: Option<Icon>,
}
impl Button {
@@ -16,6 +25,11 @@ impl Button {
base: ButtonLike::new(id),
label: label.into(),
label_color: None,
selected_label: None,
icon: None,
icon_size: None,
icon_color: None,
selected_icon: None,
}
}
@@ -23,6 +37,31 @@ impl Button {
self.label_color = label_color.into();
self
}
pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
self.selected_label = label.into().map(Into::into);
self
}
pub fn icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
self.icon = icon.into();
self
}
pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
self.icon_size = icon_size.into();
self
}
pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
self.icon_color = icon_color.into();
self
}
pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
self.selected_icon = icon.into();
self
}
}
impl Selectable for Button {
@@ -86,18 +125,35 @@ impl RenderOnce for Button {
type Rendered = ButtonLike;
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
let label_color = if self.base.disabled {
let is_disabled = self.base.disabled;
let is_selected = self.base.selected;
let label = self
.selected_label
.filter(|_| is_selected)
.unwrap_or(self.label);
let label_color = if is_disabled {
Color::Disabled
} else if self.base.selected {
} else if is_selected {
Color::Selected
} else {
self.label_color.unwrap_or_default()
};
self.base.child(
Label::new(self.label)
.color(label_color)
.line_height_style(LineHeightStyle::UILabel),
)
self.base
.children(self.icon.map(|icon| {
ButtonIcon::new(icon)
.disabled(is_disabled)
.selected(is_selected)
.selected_icon(self.selected_icon)
.size(self.icon_size)
.color(self.icon_color)
}))
.child(
Label::new(label)
.color(label_color)
.line_height_style(LineHeightStyle::UILabel),
)
}
}
@@ -0,0 +1,84 @@
use crate::{prelude::*, Icon, IconElement, IconSize};
/// An icon that appears within a button.
///
/// Can be used as either an icon alongside a label, like in [`Button`](crate::Button),
/// or as a standalone icon, like in [`IconButton`](crate::IconButton).
#[derive(IntoElement)]
pub(super) struct ButtonIcon {
icon: Icon,
size: IconSize,
color: Color,
disabled: bool,
selected: bool,
selected_icon: Option<Icon>,
}
impl ButtonIcon {
pub fn new(icon: Icon) -> Self {
Self {
icon,
size: IconSize::default(),
color: Color::default(),
disabled: false,
selected: false,
selected_icon: None,
}
}
pub fn size(mut self, size: impl Into<Option<IconSize>>) -> Self {
if let Some(size) = size.into() {
self.size = size;
}
self
}
pub fn color(mut self, color: impl Into<Option<Color>>) -> Self {
if let Some(color) = color.into() {
self.color = color;
}
self
}
pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
self.selected_icon = icon.into();
self
}
}
impl Disableable for ButtonIcon {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl Selectable for ButtonIcon {
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
}
impl RenderOnce for ButtonIcon {
type Rendered = IconElement;
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
let icon = self
.selected_icon
.filter(|_| self.selected)
.unwrap_or(self.icon);
let icon_color = if self.disabled {
Color::Disabled
} else if self.selected {
Color::Selected
} else {
self.color
};
IconElement::new(icon).size(self.size).color(icon_color)
}
}
@@ -6,18 +6,50 @@ use crate::h_stack;
use crate::prelude::*;
pub trait ButtonCommon: Clickable + Disableable {
/// A unique element ID to identify the button.
fn id(&self) -> &ElementId;
/// The visual style of the button.
///
/// Mosty commonly will be [`ButtonStyle::Subtle`], or [`ButtonStyle::Filled`]
/// for an emphasized button.
fn style(self, style: ButtonStyle) -> Self;
/// The size of the button.
///
/// Most buttons will use the default size.
///
/// [`ButtonSize`] can also be used to help build non-button elements
/// that are consistently sized with buttons.
fn size(self, size: ButtonSize) -> Self;
/// The tooltip that shows when a user hovers over the button.
///
/// Nearly all interactable elements should have a tooltip. Some example
/// exceptions might a scroll bar, or a slider.
fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum ButtonStyle {
#[default]
/// A filled button with a solid background color. Provides emphasis versus
/// the more common subtle button.
Filled,
// Tinted,
/// 🚧 Under construction 🚧
///
/// Used to emphasize a button in some way, like a selected state, or a semantic
/// coloring like an error or success button.
Tinted,
/// The default button style, used for most buttons. Has a transparent background,
/// but has a background color to indicate states like hover and active.
#[default]
Subtle,
/// Used for buttons that only change forground color on hover and active states.
///
/// TODO: Better docs for this.
Transparent,
}
@@ -41,6 +73,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background,
border_color: transparent_black(),
@@ -64,6 +102,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_hover,
border_color: transparent_black(),
@@ -89,6 +133,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_active,
border_color: transparent_black(),
@@ -115,6 +165,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background,
border_color: cx.theme().colors().border_focused,
@@ -138,6 +194,12 @@ impl ButtonStyle {
label_color: Color::Disabled.color(cx),
icon_color: Color::Disabled.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_disabled,
border_color: cx.theme().colors().border_disabled,
@@ -154,6 +216,8 @@ impl ButtonStyle {
}
}
/// ButtonSize can also be used to help build non-button elements
/// that are consistently sized with buttons.
#[derive(Default, PartialEq, Clone, Copy)]
pub enum ButtonSize {
#[default]
@@ -172,6 +236,11 @@ impl ButtonSize {
}
}
/// A button-like element that can be used to create a custom button when
/// prebuilt buttons are not sufficient. Use this sparingly, as it is
/// unconstrained and may make the UI feel less consistent.
///
/// This is also used to build the prebuilt buttons.
#[derive(IntoElement)]
pub struct ButtonLike {
id: ElementId,
@@ -272,12 +341,14 @@ impl RenderOnce for ButtonLike {
.h(self.size.height())
.when_some(self.width, |this, width| this.w(width))
.rounded_md()
.cursor_pointer()
.gap_1()
.px_1()
.bg(self.style.enabled(cx).background)
.hover(|hover| hover.bg(self.style.hovered(cx).background))
.active(|active| active.bg(self.style.active(cx).background))
.when(!self.disabled, |this| {
this.cursor_pointer()
.hover(|hover| hover.bg(self.style.hovered(cx).background))
.active(|active| active.bg(self.style.active(cx).background))
})
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| {
+17 -10
View File
@@ -1,7 +1,9 @@
use gpui::{Action, AnyView, DefiniteLength};
use crate::prelude::*;
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconElement, IconSize};
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
use super::button_icon::ButtonIcon;
#[derive(IntoElement)]
pub struct IconButton {
@@ -9,6 +11,7 @@ pub struct IconButton {
icon: Icon,
icon_size: IconSize,
icon_color: Color,
selected_icon: Option<Icon>,
}
impl IconButton {
@@ -18,6 +21,7 @@ impl IconButton {
icon,
icon_size: IconSize::default(),
icon_color: Color::Default,
selected_icon: None,
}
}
@@ -31,6 +35,11 @@ impl IconButton {
self
}
pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
self.selected_icon = icon.into();
self
}
pub fn action(self, action: Box<dyn Action>) -> Self {
self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
}
@@ -97,18 +106,16 @@ impl RenderOnce for IconButton {
type Rendered = ButtonLike;
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
let icon_color = if self.base.disabled {
Color::Disabled
} else if self.base.selected {
Color::Selected
} else {
self.icon_color
};
let is_disabled = self.base.disabled;
let is_selected = self.base.selected;
self.base.child(
IconElement::new(self.icon)
ButtonIcon::new(self.icon)
.disabled(is_disabled)
.selected(is_selected)
.selected_icon(self.selected_icon)
.size(self.icon_size)
.color(icon_color),
.color(self.icon_color),
)
}
}
+37 -7
View File
@@ -1,5 +1,6 @@
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::{
px, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
@@ -13,6 +14,7 @@ pub enum ContextMenuItem {
Header(SharedString),
Entry {
label: SharedString,
icon: Option<Icon>,
handler: Rc<dyn Fn(&mut WindowContext)>,
key_binding: Option<KeyBinding>,
},
@@ -69,6 +71,7 @@ impl ContextMenu {
label: label.into(),
handler: Rc::new(on_click),
key_binding: None,
icon: None,
});
self
}
@@ -83,6 +86,22 @@ impl ContextMenu {
label: label.into(),
key_binding: KeyBinding::for_action(&*action, cx),
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
}
@@ -175,19 +194,30 @@ impl Render for ContextMenu {
ListSubHeader::new(header.clone()).into_any_element()
}
ContextMenuItem::Entry {
label: entry,
handler: callback,
label,
handler,
key_binding,
icon,
} => {
let callback = callback.clone();
let handler = handler.clone();
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(
h_stack()
.w_full()
.justify_between()
.child(Label::new(entry.clone()))
.child(label_element)
.children(
key_binding
.clone()
@@ -196,7 +226,7 @@ impl Render for ContextMenu {
)
.selected(Some(ix) == self.selected_index)
.on_click(move |event, cx| {
callback(cx);
handler(cx);
dismiss(event, cx)
})
.into_any_element()
+2
View File
@@ -55,6 +55,7 @@ pub enum Icon {
FolderX,
Hash,
InlayHint,
Link,
MagicWand,
MagnifyingGlass,
MailOpen,
@@ -128,6 +129,7 @@ impl Icon {
Icon::FolderX => "icons/stop_sharing.svg",
Icon::Hash => "icons/hash.svg",
Icon::InlayHint => "icons/inlay_hint.svg",
Icon::Link => "icons/link.svg",
Icon::MagicWand => "icons/magic-wand.svg",
Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
Icon::MailOpen => "icons/mail-open.svg",
+23 -27
View File
@@ -1,6 +1,8 @@
use std::ops::Range;
use crate::prelude::*;
use crate::styled_ext::StyledExt;
use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext};
use gpui::{relative, Div, HighlightStyle, IntoElement, StyledText, WindowContext};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum LabelSize {
@@ -99,38 +101,32 @@ impl RenderOnce for HighlightedLabel {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let highlight_color = cx.theme().colors().text_accent;
let mut text_style = cx.text_style().clone();
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
let mut runs: Vec<TextRun> = Vec::new();
while let Some(start_ix) = highlight_indices.next() {
let mut end_ix = start_ix;
for (char_ix, char) in self.label.char_indices() {
let mut color = self.color.color(cx);
if let Some(highlight_ix) = highlight_indices.peek() {
if char_ix == *highlight_ix {
color = highlight_color;
highlight_indices.next();
loop {
end_ix = end_ix + self.label[end_ix..].chars().next().unwrap().len_utf8();
if let Some(&next_ix) = highlight_indices.peek() {
if next_ix == end_ix {
end_ix = next_ix;
highlight_indices.next();
continue;
}
}
break;
}
let last_run = runs.last_mut();
let start_new_run = if let Some(last_run) = last_run {
if color == last_run.color {
last_run.len += char.len_utf8();
false
} else {
true
}
} else {
true
};
if start_new_run {
text_style.color = color;
runs.push(text_style.to_run(char.len_utf8()))
}
highlights.push((
start_ix..end_ix,
HighlightStyle {
color: Some(highlight_color),
..Default::default()
},
));
}
div()
@@ -150,7 +146,7 @@ impl RenderOnce for HighlightedLabel {
LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(),
})
.child(StyledText::new(self.label).with_runs(runs))
.child(StyledText::new(self.label).with_highlights(&cx.text_style(), highlights))
}
}
+15 -1
View File
@@ -1,7 +1,7 @@
use gpui::{Div, Render};
use story::Story;
use crate::prelude::*;
use crate::{prelude::*, Icon};
use crate::{Button, ButtonStyle};
pub struct ButtonStory;
@@ -16,8 +16,22 @@ impl Render for ButtonStory {
.child(Button::new("default_filled", "Click me"))
.child(Story::label("Selected"))
.child(Button::new("selected_filled", "Click me").selected(true))
.child(Story::label("Selected with `selected_label`"))
.child(
Button::new("selected_label_filled", "Click me")
.selected(true)
.selected_label("I have been selected"),
)
.child(Story::label("With `label_color`"))
.child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
.child(Story::label("With `icon`"))
.child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit))
.child(Story::label("Selected with `icon`"))
.child(
Button::new("filled_and_selected_with_icon", "Click me")
.selected(true)
.icon(Icon::FileGit),
)
.child(Story::label("Default (Subtle)"))
.child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))
.child(Story::label("Default (Transparent)"))
@@ -20,6 +20,14 @@ impl Render for IconButtonStory {
.w_8()
.child(IconButton::new("icon_a", Icon::Hash).selected(true)),
)
.child(Story::label("Selected with `selected_icon`"))
.child(
div().w_8().child(
IconButton::new("icon_a", Icon::AudioOn)
.selected(true)
.selected_icon(Icon::AudioOff),
),
)
.child(Story::label("Disabled"))
.child(
div()
+16 -41
View File
@@ -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
+263 -80
View File
@@ -2,14 +2,15 @@ use crate::{
item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
toolbar::Toolbar,
workspace_settings::{AutosaveSetting, WorkspaceSettings},
SplitDirection, Workspace,
NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
};
use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque};
use gpui::{
actions, prelude::*, Action, AppContext, AsyncWindowContext, Div, EntityId, EventEmitter,
FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View,
ViewContext, VisualContext, WeakView, WindowContext,
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,
};
use parking_lot::Mutex;
use project::{Project, ProjectEntryId, ProjectPath};
@@ -25,8 +26,10 @@ use std::{
},
};
use ui::v_stack;
use ui::{prelude::*, Color, Icon, IconButton, IconElement, Tooltip};
use ui::{
h_stack, prelude::*, right_click_menu, Color, Icon, IconButton, IconElement, Label, Tooltip,
};
use ui::{v_stack, ContextMenu};
use util::truncate_and_remove_front;
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
@@ -50,7 +53,7 @@ pub enum SaveIntent {
//todo!("Do we need the default bound on actions? Decide soon")
// #[register_action]
#[derive(Clone, Deserialize, PartialEq, Debug)]
#[derive(Action, Clone, Deserialize, PartialEq, Debug)]
pub struct ActivateItem(pub usize);
// #[derive(Clone, PartialEq)]
@@ -143,17 +146,24 @@ impl fmt::Debug for Event {
}
}
struct FocusedView {
view: AnyWeakView,
focus_handle: FocusHandle,
}
pub struct Pane {
focus_handle: FocusHandle,
items: Vec<Box<dyn ItemHandle>>,
activation_history: Vec<EntityId>,
zoomed: bool,
active_item_index: usize,
// last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
last_focused_view_by_item: HashMap<EntityId, FocusHandle>,
autoscroll: bool,
nav_history: NavHistory,
toolbar: View<Toolbar>,
// tab_bar_context_menu: TabBarContextMenu,
tab_bar_focus_handle: FocusHandle,
new_item_menu: Option<View<ContextMenu>>,
split_item_menu: Option<View<ContextMenu>>,
// tab_context_menu: ViewHandle<ContextMenu>,
workspace: WeakView<Workspace>,
project: Model<Project>,
@@ -306,7 +316,7 @@ impl Pane {
activation_history: Vec::new(),
zoomed: false,
active_item_index: 0,
// last_focused_view_by_item: Default::default(),
last_focused_view_by_item: Default::default(),
autoscroll: false,
nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
mode: NavigationMode::Normal,
@@ -318,6 +328,9 @@ impl Pane {
next_timestamp,
}))),
toolbar: cx.build_view(|_| Toolbar::new()),
tab_bar_focus_handle: cx.focus_handle(),
new_item_menu: None,
split_item_menu: None,
// tab_bar_context_menu: TabBarContextMenu {
// kind: TabBarContextMenuKind::New,
// handle: context_menu,
@@ -392,9 +405,48 @@ impl Pane {
}
pub fn has_focus(&self, cx: &WindowContext) -> bool {
// todo!(); // inline this manually
self.focus_handle.contains_focused(cx)
}
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
if !self.has_focus(cx) {
cx.emit(Event::Focus);
cx.notify();
}
self.toolbar.update(cx, |toolbar, cx| {
toolbar.focus_changed(true, cx);
});
if let Some(active_item) = self.active_item() {
if self.focus_handle.is_focused(cx) {
// Pane was focused directly. We need to either focus a view inside the active item,
// or focus the active item itself
if let Some(weak_last_focused_view) =
self.last_focused_view_by_item.get(&active_item.item_id())
{
weak_last_focused_view.focus(cx);
return;
}
active_item.focus_handle(cx).focus(cx);
} else if !self.tab_bar_focus_handle.contains_focused(cx) {
if let Some(focused) = cx.focused() {
self.last_focused_view_by_item
.insert(active_item.item_id(), focused);
}
}
}
}
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
self.toolbar.update(cx, |toolbar, cx| {
toolbar.focus_changed(false, cx);
});
cx.notify();
}
pub fn active_item_index(&self) -> usize {
self.active_item_index
}
@@ -652,21 +704,16 @@ impl Pane {
.position(|i| i.item_id() == item.item_id())
}
// pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
// // Potentially warn the user of the new keybinding
// let workspace_handle = self.workspace().clone();
// cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
// .detach();
// if self.zoomed {
// cx.emit(Event::ZoomOut);
// } else if !self.items.is_empty() {
// if !self.has_focus {
// cx.focus_self();
// }
// cx.emit(Event::ZoomIn);
// }
// }
pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
if self.zoomed {
cx.emit(Event::ZoomOut);
} else if !self.items.is_empty() {
if !self.focus_handle.contains_focused(cx) {
cx.focus_self();
}
cx.emit(Event::ZoomIn);
}
}
pub fn activate_item(
&mut self,
@@ -1403,7 +1450,7 @@ impl Pane {
let close_right = ItemSettings::get_global(cx).close_position.right();
let is_active = ix == self.active_item_index;
div()
let tab = div()
.group("")
.id(ix)
.cursor_pointer()
@@ -1477,51 +1524,75 @@ impl Pane {
.children((!close_right).then(|| close_icon()))
.child(label)
.children(close_right.then(|| close_icon())),
)
);
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,
)
})
})
}
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()
.px_2()
.flex()
.flex_none()
.gap_2()
.gap_1()
// 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()),
),
),
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()),
),
),
)
.child(
@@ -1550,20 +1621,87 @@ impl Pane {
.gap_px()
.child(
div()
.bg(gpui::blue())
.border()
.border_color(gpui::red())
.child(IconButton::new("plus", Icon::Plus)),
.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)),
.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,
)
.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))
},
),
),
),
)
}
fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
div()
.absolute()
.z_index(1)
.bottom_0()
.right_0()
.size_0()
.child(overlay().anchor(AnchorCorner::TopRight).child(menu.clone()))
}
// fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
// let theme = theme::current(cx).clone();
@@ -1962,9 +2100,23 @@ impl Render for Pane {
type Element = Focusable<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let this = cx.view().downgrade();
v_stack()
.key_context("Pane")
.track_focus(&self.focus_handle)
.on_focus_in({
let this = this.clone();
move |event, cx| {
this.update(cx, |this, cx| this.focus_in(cx)).ok();
}
})
.on_focus_out({
let this = this.clone();
move |event, cx| {
this.update(cx, |this, cx| this.focus_out(cx)).ok();
}
})
.on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
.on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
.on_action(
@@ -1973,25 +2125,53 @@ impl Render for Pane {
.on_action(cx.listener(|pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)))
.on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx)))
.on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx)))
// cx.add_action(Pane::toggle_zoom);
// cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
// pane.activate_item(action.0, true, true, cx);
// });
// cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
// pane.activate_item(pane.items.len() - 1, true, true, cx);
// });
// cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
// pane.activate_prev_item(true, cx);
// });
// cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
// pane.activate_next_item(true, cx);
// });
// cx.add_async_action(Pane::close_active_item);
// cx.add_async_action(Pane::close_inactive_items);
// cx.add_async_action(Pane::close_clean_items);
// cx.add_async_action(Pane::close_items_to_the_left);
// cx.add_async_action(Pane::close_items_to_the_right);
// cx.add_async_action(Pane::close_all_items);
.on_action(cx.listener(Pane::toggle_zoom))
.on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
pane.activate_item(action.0, true, true, cx);
}))
.on_action(cx.listener(|pane: &mut Pane, _: &ActivateLastItem, cx| {
pane.activate_item(pane.items.len() - 1, true, true, cx);
}))
.on_action(cx.listener(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
pane.activate_prev_item(true, cx);
}))
.on_action(cx.listener(|pane: &mut Pane, _: &ActivateNextItem, cx| {
pane.activate_next_item(true, cx);
}))
.on_action(
cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
pane.close_active_item(action, cx)
.map(|task| task.detach_and_log_err(cx));
}),
)
.on_action(
cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| {
pane.close_inactive_items(action, cx)
.map(|task| task.detach_and_log_err(cx));
}),
)
.on_action(
cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| {
pane.close_clean_items(action, cx)
.map(|task| task.detach_and_log_err(cx));
}),
)
.on_action(
cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| {
pane.close_items_to_the_left(action, cx)
.map(|task| task.detach_and_log_err(cx));
}),
)
.on_action(
cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| {
pane.close_items_to_the_right(action, cx)
.map(|task| task.detach_and_log_err(cx));
}),
)
.on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| {
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| {
@@ -2004,8 +2184,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 {}
+3 -31
View File
@@ -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(
+3 -13
View File
@@ -4,7 +4,7 @@ use gpui::{
ViewContext, WindowContext,
};
use ui::prelude::*;
use ui::{h_stack, v_stack, ButtonLike, Color, Icon, IconButton, Label};
use ui::{h_stack, v_stack, Icon, IconButton};
pub enum ToolbarItemEvent {
ChangeLocation(ToolbarItemLocation),
@@ -87,17 +87,8 @@ impl Render for Toolbar {
.child(
h_stack()
.justify_between()
.child(
// Toolbar left side
h_stack().border().border_color(gpui::red()).p_1().child(
ButtonLike::new("breadcrumb")
.child(Label::new("crates/workspace2/src/toolbar.rs"))
.child(Label::new("").color(Color::Muted))
.child(Label::new("impl Render for Toolbar"))
.child(Label::new("").color(Color::Muted))
.child(Label::new("fn render")),
),
)
// Toolbar left side
.children(self.items.iter().map(|(child, _)| child.to_any()))
// Toolbar right side
.child(
h_stack()
@@ -116,7 +107,6 @@ impl Render for Toolbar {
),
),
)
.children(self.items.iter().map(|(child, _)| child.to_any()))
}
}
-81
View File
@@ -3585,87 +3585,6 @@ fn open_items(
})
}
// todo!()
// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
// const MESSAGE_ID: usize = 2;
// if workspace
// .read_with(cx, |workspace, cx| {
// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
// })
// .unwrap_or(false)
// {
// return;
// }
// if db::kvp::KEY_VALUE_STORE
// .read_kvp(NEW_DOCK_HINT_KEY)
// .ok()
// .flatten()
// .is_some()
// {
// if !workspace
// .read_with(cx, |workspace, cx| {
// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
// })
// .unwrap_or(false)
// {
// cx.update(|cx| {
// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
// let entry = tracker
// .entry(TypeId::of::<MessageNotification>())
// .or_default();
// if !entry.contains(&MESSAGE_ID) {
// entry.push(MESSAGE_ID);
// }
// });
// });
// }
// return;
// }
// cx.spawn(|_| async move {
// db::kvp::KEY_VALUE_STORE
// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
// .await
// .ok();
// })
// .detach();
// workspace
// .update(cx, |workspace, cx| {
// workspace.show_notification_once(2, cx, |cx| {
// cx.build_view(|_| {
// MessageNotification::new_element(|text, _| {
// Text::new(
// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
// text,
// )
// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
// let code_span_background_color = settings::get::<ThemeSettings>(cx)
// .theme
// .editor
// .document_highlight_read_background;
// cx.scene().push_quad(gpui::Quad {
// bounds,
// background: Some(code_span_background_color),
// border: Default::default(),
// corner_radii: (2.0).into(),
// })
// })
// .into_any()
// })
// .with_click_message("Read more about the new panel system")
// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
// })
// })
// })
// .ok();
fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
+1 -1
View File
@@ -39,7 +39,7 @@ pub enum IsOnlyInstance {
}
pub fn ensure_only_instance() -> IsOnlyInstance {
if *db::ZED_STATELESS {
if *db::ZED_STATELESS || *util::channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
return IsOnlyInstance::Yes;
}
+3 -3
View File
@@ -19,7 +19,7 @@ ai = { package = "ai2", path = "../ai2"}
audio = { package = "audio2", path = "../audio2" }
activity_indicator = { package = "activity_indicator2", path = "../activity_indicator2"}
auto_update = { package = "auto_update2", path = "../auto_update2" }
# breadcrumbs = { path = "../breadcrumbs" }
breadcrumbs = { package = "breadcrumbs2", path = "../breadcrumbs2" }
call = { package = "call2", path = "../call2" }
channel = { package = "channel2", path = "../channel2" }
cli = { path = "../cli" }
@@ -30,7 +30,7 @@ command_palette = { package="command_palette2", path = "../command_palette2" }
client = { package = "client2", path = "../client2" }
# clock = { path = "../clock" }
copilot = { package = "copilot2", path = "../copilot2" }
# copilot_button = { path = "../copilot_button" }
copilot_button = { package = "copilot_button2", path = "../copilot_button2" }
diagnostics = { package = "diagnostics2", path = "../diagnostics2" }
db = { package = "db2", path = "../db2" }
editor = { package="editor2", path = "../editor2" }
@@ -44,7 +44,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
install_cli = { package = "install_cli2", path = "../install_cli2" }
journal = { package = "journal2", path = "../journal2" }
language = { package = "language2", path = "../language2" }
# language_selector = { path = "../language_selector" }
language_selector = { package = "language_selector2", path = "../language_selector2" }
lsp = { package = "lsp2", path = "../lsp2" }
menu = { package = "menu2", path = "../menu2" }
# language_tools = { path = "../language_tools" }
+1 -1
View File
@@ -216,7 +216,7 @@ fn main() {
terminal_view::init(cx);
// journal2::init(app_state.clone(), cx);
// language_selector::init(cx);
language_selector::init(cx);
theme_selector::init(cx);
// activity_indicator::init(cx);
// language_tools::init(cx);
+1 -1
View File
@@ -39,7 +39,7 @@ pub enum IsOnlyInstance {
}
pub fn ensure_only_instance() -> IsOnlyInstance {
if *db::ZED_STATELESS {
if *db::ZED_STATELESS || *util::channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
return IsOnlyInstance::Yes;
}
+10 -9
View File
@@ -7,6 +7,7 @@ mod only_instance;
mod open_listener;
pub use assets::*;
use breadcrumbs::Breadcrumbs;
use collections::VecDeque;
use editor::{Editor, MultiBuffer};
use gpui::{
@@ -95,11 +96,11 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
if let workspace::Event::PaneAdded(pane) = event {
pane.update(cx, |pane, cx| {
pane.toolbar().update(cx, |toolbar, cx| {
// todo!()
// let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
// toolbar.add_item(breadcrumbs, cx);
let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace));
toolbar.add_item(breadcrumbs, cx);
let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
toolbar.add_item(buffer_search_bar.clone(), cx);
// todo!()
// let quick_action_bar = cx.add_view(|_| {
// QuickActionBar::new(buffer_search_bar, workspace)
// });
@@ -135,14 +136,14 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
// cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
// workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
// let copilot =
// cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
let copilot =
cx.build_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
let diagnostic_summary =
cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
let activity_indicator =
activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
// let active_buffer_language =
// cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
let active_buffer_language =
cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
// let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
// let feedback_button = cx.add_view(|_| {
// feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
@@ -153,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_right_item(feedback_button, cx);
// status_bar.add_right_item(copilot, cx);
// status_bar.add_right_item(active_buffer_language, cx);
status_bar.add_right_item(copilot, cx);
status_bar.add_right_item(active_buffer_language, cx);
// status_bar.add_right_item(vim_mode_indicator, cx);
status_bar.add_right_item(cursor_position, cx);
});
-5618
View File
File diff suppressed because it is too large Load Diff