From b73ccc8180ca4f46027b874ec205ef4461045547 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 5 Dec 2023 14:53:01 +0100 Subject: [PATCH 01/23] Start out Copilot2; Add hidden_action_types to CommandPaletteFilter. WindowContext.available_actions now returns global actions as well. Co-authored-by: Antonio --- crates/collections/src/collections.rs | 4 +- crates/command_palette/src/command_palette.rs | 4 +- .../command_palette2/src/command_palette.rs | 7 +- crates/copilot/src/copilot.rs | 12 +- crates/copilot2/src/copilot2.rs | 119 ++-- crates/copilot2/src/sign_in.rs | 587 +++++++++--------- crates/gpui2/src/window.rs | 12 +- crates/vim/src/vim.rs | 6 +- 8 files changed, 391 insertions(+), 360 deletions(-) diff --git a/crates/collections/src/collections.rs b/crates/collections/src/collections.rs index eb4e4d8462720c773d7e48b92f413cad2ca1970a..bffa5c877a2e33788da4c5bb4d3f7d2918dbe2ea 100644 --- a/crates/collections/src/collections.rs +++ b/crates/collections/src/collections.rs @@ -23,11 +23,13 @@ pub type HashMap = std::collections::HashMap; #[cfg(not(feature = "test-support"))] pub type HashSet = std::collections::HashSet; +use std::any::TypeId; pub use std::collections::*; // NEW TYPES #[derive(Default)] pub struct CommandPaletteFilter { - pub filtered_namespaces: HashSet<&'static str>, + pub hidden_namespaces: HashSet<&'static str>, + pub hidden_action_types: HashSet, } diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index ce762876a41238ab4e0f51ddcd29155b39274840..356300052e4c420ed872ac21f3c7f3da541c5ad8 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -109,7 +109,7 @@ impl PickerDelegate for CommandPaletteDelegate { let filtered = cx.read(|cx| { if cx.has_global::() { let filter = cx.global::(); - filter.filtered_namespaces.contains(action.namespace()) + filter.hidden_namespaces.contains(action.namespace()) } else { false } @@ -430,7 +430,7 @@ mod tests { // Add namespace filter, and redeploy the palette cx.update(|cx| { cx.update_default_global::(|filter, _| { - filter.filtered_namespaces.insert("editor"); + filter.hidden_namespaces.insert("editor"); }) }); diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 04688b05492c8c298c33d32423cdb5e7ce1fe393..f94b5e77caa5cb3676073147555753471db13c98 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -49,7 +49,10 @@ impl CommandPalette { .filter_map(|action| { let name = gpui::remove_the_2(action.name()); let namespace = name.split("::").next().unwrap_or("malformed action name"); - if filter.is_some_and(|f| f.filtered_namespaces.contains(namespace)) { + if filter.is_some_and(|f| { + f.hidden_namespaces.contains(namespace) + || f.hidden_action_types.contains(&action.type_id()) + }) { return None; } @@ -429,7 +432,7 @@ mod tests { cx.update(|cx| { cx.set_global(CommandPaletteFilter::default()); cx.update_global::(|filter, _| { - filter.filtered_namespaces.insert("editor"); + filter.hidden_namespaces.insert("editor"); }) }); diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 92d430e3fb06699ff86fba1f7c4e6de8eb222182..0c6f7e3907000cdcc8a9de5317a307de4421757d 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -58,16 +58,16 @@ pub fn init( cx.update_default_global::(move |filter, _cx| { match status { Status::Disabled => { - filter.filtered_namespaces.insert(COPILOT_NAMESPACE); - filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE); + filter.hidden_namespaces.insert(COPILOT_NAMESPACE); + filter.hidden_namespaces.insert(COPILOT_AUTH_NAMESPACE); } Status::Authorized => { - filter.filtered_namespaces.remove(COPILOT_NAMESPACE); - filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE); + filter.hidden_namespaces.remove(COPILOT_NAMESPACE); + filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE); } _ => { - filter.filtered_namespaces.insert(COPILOT_NAMESPACE); - filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE); + filter.hidden_namespaces.insert(COPILOT_NAMESPACE); + filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE); } } }); diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index b2454728644b212c8736b49876b213123cf039e8..d23d25119b152be1c232304d2992af81af3ab9e4 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/crates/copilot2/src/copilot2.rs @@ -22,6 +22,7 @@ use request::StatusNotification; use settings::SettingsStore; use smol::{fs, io::BufReader, stream::StreamExt}; use std::{ + any::TypeId, ffi::OsString, mem, ops::Range, @@ -32,13 +33,14 @@ use util::{ fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt, }; -// todo!() -// const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth"; -actions!(SignIn, SignOut); - -// todo!() -// const COPILOT_NAMESPACE: &'static str = "copilot"; -actions!(Suggest, NextSuggestion, PreviousSuggestion, Reinstall); +actions!( + Suggest, + NextSuggestion, + PreviousSuggestion, + Reinstall, + SignIn, + SignOut +); pub fn init( new_server_id: LanguageServerId, @@ -51,52 +53,63 @@ pub fn init( move |cx| Copilot::start(new_server_id, http, node_runtime, cx) }); cx.set_global(copilot.clone()); - - // TODO - // cx.observe(&copilot, |handle, cx| { - // let status = handle.read(cx).status(); - // cx.update_default_global::(move |filter, _cx| { - // match status { - // Status::Disabled => { - // filter.filtered_namespaces.insert(COPILOT_NAMESPACE); - // filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE); - // } - // Status::Authorized => { - // filter.filtered_namespaces.remove(COPILOT_NAMESPACE); - // filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE); - // } - // _ => { - // filter.filtered_namespaces.insert(COPILOT_NAMESPACE); - // filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE); - // } - // } - // }); - // }) - // .detach(); - - // sign_in::init(cx); - // cx.add_global_action(|_: &SignIn, cx| { - // if let Some(copilot) = Copilot::global(cx) { - // copilot - // .update(cx, |copilot, cx| copilot.sign_in(cx)) - // .detach_and_log_err(cx); - // } - // }); - // cx.add_global_action(|_: &SignOut, cx| { - // if let Some(copilot) = Copilot::global(cx) { - // copilot - // .update(cx, |copilot, cx| copilot.sign_out(cx)) - // .detach_and_log_err(cx); - // } - // }); - - // cx.add_global_action(|_: &Reinstall, cx| { - // if let Some(copilot) = Copilot::global(cx) { - // copilot - // .update(cx, |copilot, cx| copilot.reinstall(cx)) - // .detach(); - // } - // }); + cx.observe(&copilot, |handle, cx| { + let copilot_action_types = [ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ]; + let copilot_auth_action_types = [TypeId::of::(), TypeId::of::()]; + + let status = handle.read(cx).status(); + let filter = cx.default_global::(); + + match status { + Status::Disabled => { + filter.hidden_action_types.extend(copilot_action_types); + filter.hidden_action_types.extend(copilot_auth_action_types); + } + Status::Authorized => { + for type_id in copilot_action_types + .iter() + .chain(&copilot_auth_action_types) + { + filter.hidden_action_types.remove(type_id); + } + } + _ => { + filter.hidden_action_types.extend(copilot_action_types); + for type_id in &copilot_auth_action_types { + filter.hidden_action_types.remove(type_id); + } + } + } + }) + .detach(); + + sign_in::init(cx); + cx.on_action(|_: &SignIn, cx| { + if let Some(copilot) = Copilot::global(cx) { + copilot + .update(cx, |copilot, cx| copilot.sign_in(cx)) + .detach_and_log_err(cx); + } + }); + cx.on_action(|_: &SignOut, cx| { + if let Some(copilot) = Copilot::global(cx) { + copilot + .update(cx, |copilot, cx| copilot.sign_out(cx)) + .detach_and_log_err(cx); + } + }); + cx.on_action(|_: &Reinstall, cx| { + if let Some(copilot) = Copilot::global(cx) { + copilot + .update(cx, |copilot, cx| copilot.reinstall(cx)) + .detach(); + } + }); } enum CopilotServer { diff --git a/crates/copilot2/src/sign_in.rs b/crates/copilot2/src/sign_in.rs index 57f248aa52486d8c04672eeef8d33e5acda2a52c..7973d935af1be1c455bb2b90a0ed8dcf2c1b3566 100644 --- a/crates/copilot2/src/sign_in.rs +++ b/crates/copilot2/src/sign_in.rs @@ -9,314 +9,319 @@ // }; // use theme::ui::modal; -// #[derive(PartialEq, Eq, Debug, Clone)] -// struct CopyUserCode; +const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; -// #[derive(PartialEq, Eq, Debug, Clone)] -// struct OpenGithub; +use crate::{Copilot, Status}; +use gpui::{ + px, size, AppContext, Bounds, Div, GlobalPixels, Point, Render, ViewContext, VisualContext, + WindowBounds, WindowHandle, WindowKind, WindowOptions, +}; -// const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; +pub fn init(cx: &mut AppContext) { + if let Some(copilot) = Copilot::global(cx) { + let mut verification_window: Option> = None; + cx.observe(&copilot, move |copilot, cx| { + let status = copilot.read(cx).status(); -// pub fn init(cx: &mut AppContext) { -// if let Some(copilot) = Copilot::global(cx) { -// let mut verification_window: Option> = None; -// cx.observe(&copilot, move |copilot, cx| { -// let status = copilot.read(cx).status(); + match &status { + crate::Status::SigningIn { prompt } => { + if let Some(window) = verification_window.as_mut() { + let updated = window + .update(cx, |verification, cx| { + verification.set_status(status.clone(), cx); + cx.activate_window(); + }) + .is_ok(); + if !updated { + verification_window = Some(create_copilot_auth_window(cx, &status)); + } + } else if let Some(_prompt) = prompt { + verification_window = Some(create_copilot_auth_window(cx, &status)); + } + } + Status::Authorized | Status::Unauthorized => { + if let Some(window) = verification_window.as_ref() { + window + .update(cx, |verification, cx| { + verification.set_status(status, cx); + cx.activate(true); + cx.activate_window(); + }) + .ok(); + } + } + _ => { + if let Some(code_verification) = verification_window.take() { + code_verification.update(cx, |_, cx| cx.remove_window()); + } + } + } + }) + .detach(); + } +} -// match &status { -// crate::Status::SigningIn { prompt } => { -// if let Some(window) = verification_window.as_mut() { -// let updated = window -// .root(cx) -// .map(|root| { -// root.update(cx, |verification, cx| { -// verification.set_status(status.clone(), cx); -// cx.activate_window(); -// }) -// }) -// .is_some(); -// if !updated { -// verification_window = Some(create_copilot_auth_window(cx, &status)); -// } -// } else if let Some(_prompt) = prompt { -// verification_window = Some(create_copilot_auth_window(cx, &status)); -// } -// } -// Status::Authorized | Status::Unauthorized => { -// if let Some(window) = verification_window.as_ref() { -// if let Some(verification) = window.root(cx) { -// verification.update(cx, |verification, cx| { -// verification.set_status(status, cx); -// cx.platform().activate(true); -// cx.activate_window(); -// }); -// } -// } -// } -// _ => { -// if let Some(code_verification) = verification_window.take() { -// code_verification.update(cx, |cx| cx.remove_window()); -// } -// } -// } -// }) -// .detach(); -// } -// } +fn create_copilot_auth_window( + cx: &mut AppContext, + status: &Status, +) -> WindowHandle { + let window_size = size(GlobalPixels::from(280.), GlobalPixels::from(280.)); + let window_options = WindowOptions { + bounds: WindowBounds::Fixed(Bounds::new(Point::default(), window_size)), + titlebar: None, + center: true, + focus: true, + show: true, + kind: WindowKind::Normal, + is_movable: true, + display_id: None, + }; + cx.open_window(window_options, |cx| { + cx.build_view(|_| CopilotCodeVerification::new(status.clone())) + }) +} -// fn create_copilot_auth_window( -// cx: &mut AppContext, -// status: &Status, -// ) -> WindowHandle { -// let window_size = theme::current(cx).copilot.modal.dimensions(); -// let window_options = WindowOptions { -// bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)), -// titlebar: None, -// center: true, -// focus: true, -// show: true, -// kind: WindowKind::Normal, -// is_movable: true, -// screen: None, -// }; -// cx.add_window(window_options, |_cx| { -// CopilotCodeVerification::new(status.clone()) -// }) -// } +pub struct CopilotCodeVerification { + status: Status, + connect_clicked: bool, +} -// pub struct CopilotCodeVerification { -// status: Status, -// connect_clicked: bool, -// } +impl CopilotCodeVerification { + pub fn new(status: Status) -> Self { + Self { + status, + connect_clicked: false, + } + } -// impl CopilotCodeVerification { -// pub fn new(status: Status) -> Self { -// Self { -// status, -// connect_clicked: false, -// } -// } + pub fn set_status(&mut self, status: Status, cx: &mut ViewContext) { + self.status = status; + cx.notify(); + } -// pub fn set_status(&mut self, status: Status, cx: &mut ViewContext) { -// self.status = status; -// cx.notify(); -// } + // fn render_device_code( + // data: &PromptUserDeviceFlow, + // style: &theme::Copilot, + // cx: &mut ViewContext, + // ) -> impl IntoAnyElement { + // let copied = cx + // .read_from_clipboard() + // .map(|item| item.text() == &data.user_code) + // .unwrap_or(false); -// fn render_device_code( -// data: &PromptUserDeviceFlow, -// style: &theme::Copilot, -// cx: &mut ViewContext, -// ) -> impl IntoAnyElement { -// let copied = cx -// .read_from_clipboard() -// .map(|item| item.text() == &data.user_code) -// .unwrap_or(false); + // let device_code_style = &style.auth.prompting.device_code; -// let device_code_style = &style.auth.prompting.device_code; + // MouseEventHandler::new::(0, cx, |state, _cx| { + // Flex::row() + // .with_child( + // Label::new(data.user_code.clone(), device_code_style.text.clone()) + // .aligned() + // .contained() + // .with_style(device_code_style.left_container) + // .constrained() + // .with_width(device_code_style.left), + // ) + // .with_child( + // Label::new( + // if copied { "Copied!" } else { "Copy" }, + // device_code_style.cta.style_for(state).text.clone(), + // ) + // .aligned() + // .contained() + // .with_style(*device_code_style.right_container.style_for(state)) + // .constrained() + // .with_width(device_code_style.right), + // ) + // .contained() + // .with_style(device_code_style.cta.style_for(state).container) + // }) + // .on_click(gpui::platform::MouseButton::Left, { + // let user_code = data.user_code.clone(); + // move |_, _, cx| { + // cx.platform() + // .write_to_clipboard(ClipboardItem::new(user_code.clone())); + // cx.notify(); + // } + // }) + // .with_cursor_style(gpui::platform::CursorStyle::PointingHand) + // } -// MouseEventHandler::new::(0, cx, |state, _cx| { -// Flex::row() -// .with_child( -// Label::new(data.user_code.clone(), device_code_style.text.clone()) -// .aligned() -// .contained() -// .with_style(device_code_style.left_container) -// .constrained() -// .with_width(device_code_style.left), -// ) -// .with_child( -// Label::new( -// if copied { "Copied!" } else { "Copy" }, -// device_code_style.cta.style_for(state).text.clone(), -// ) -// .aligned() -// .contained() -// .with_style(*device_code_style.right_container.style_for(state)) -// .constrained() -// .with_width(device_code_style.right), -// ) -// .contained() -// .with_style(device_code_style.cta.style_for(state).container) -// }) -// .on_click(gpui::platform::MouseButton::Left, { -// let user_code = data.user_code.clone(); -// move |_, _, cx| { -// cx.platform() -// .write_to_clipboard(ClipboardItem::new(user_code.clone())); -// cx.notify(); -// } -// }) -// .with_cursor_style(gpui::platform::CursorStyle::PointingHand) -// } + // fn render_prompting_modal( + // connect_clicked: bool, + // data: &PromptUserDeviceFlow, + // style: &theme::Copilot, + // cx: &mut ViewContext, + // ) -> AnyElement { + // enum ConnectButton {} -// fn render_prompting_modal( -// connect_clicked: bool, -// data: &PromptUserDeviceFlow, -// style: &theme::Copilot, -// cx: &mut ViewContext, -// ) -> AnyElement { -// enum ConnectButton {} + // Flex::column() + // .with_child( + // Flex::column() + // .with_children([ + // Label::new( + // "Enable Copilot by connecting", + // style.auth.prompting.subheading.text.clone(), + // ) + // .aligned(), + // Label::new( + // "your existing license.", + // style.auth.prompting.subheading.text.clone(), + // ) + // .aligned(), + // ]) + // .align_children_center() + // .contained() + // .with_style(style.auth.prompting.subheading.container), + // ) + // .with_child(Self::render_device_code(data, &style, cx)) + // .with_child( + // Flex::column() + // .with_children([ + // Label::new( + // "Paste this code into GitHub after", + // style.auth.prompting.hint.text.clone(), + // ) + // .aligned(), + // Label::new( + // "clicking the button below.", + // style.auth.prompting.hint.text.clone(), + // ) + // .aligned(), + // ]) + // .align_children_center() + // .contained() + // .with_style(style.auth.prompting.hint.container.clone()), + // ) + // .with_child(theme::ui::cta_button::( + // if connect_clicked { + // "Waiting for connection..." + // } else { + // "Connect to GitHub" + // }, + // style.auth.content_width, + // &style.auth.cta_button, + // cx, + // { + // let verification_uri = data.verification_uri.clone(); + // move |_, verification, cx| { + // cx.platform().open_url(&verification_uri); + // verification.connect_clicked = true; + // } + // }, + // )) + // .align_children_center() + // .into_any() + // } -// Flex::column() -// .with_child( -// Flex::column() -// .with_children([ -// Label::new( -// "Enable Copilot by connecting", -// style.auth.prompting.subheading.text.clone(), -// ) -// .aligned(), -// Label::new( -// "your existing license.", -// style.auth.prompting.subheading.text.clone(), -// ) -// .aligned(), -// ]) -// .align_children_center() -// .contained() -// .with_style(style.auth.prompting.subheading.container), -// ) -// .with_child(Self::render_device_code(data, &style, cx)) -// .with_child( -// Flex::column() -// .with_children([ -// Label::new( -// "Paste this code into GitHub after", -// style.auth.prompting.hint.text.clone(), -// ) -// .aligned(), -// Label::new( -// "clicking the button below.", -// style.auth.prompting.hint.text.clone(), -// ) -// .aligned(), -// ]) -// .align_children_center() -// .contained() -// .with_style(style.auth.prompting.hint.container.clone()), -// ) -// .with_child(theme::ui::cta_button::( -// if connect_clicked { -// "Waiting for connection..." -// } else { -// "Connect to GitHub" -// }, -// style.auth.content_width, -// &style.auth.cta_button, -// cx, -// { -// let verification_uri = data.verification_uri.clone(); -// move |_, verification, cx| { -// cx.platform().open_url(&verification_uri); -// verification.connect_clicked = true; -// } -// }, -// )) -// .align_children_center() -// .into_any() -// } + // fn render_enabled_modal( + // style: &theme::Copilot, + // cx: &mut ViewContext, + // ) -> AnyElement { + // enum DoneButton {} -// fn render_enabled_modal( -// style: &theme::Copilot, -// cx: &mut ViewContext, -// ) -> AnyElement { -// enum DoneButton {} + // let enabled_style = &style.auth.authorized; + // Flex::column() + // .with_child( + // Label::new("Copilot Enabled!", enabled_style.subheading.text.clone()) + // .contained() + // .with_style(enabled_style.subheading.container) + // .aligned(), + // ) + // .with_child( + // Flex::column() + // .with_children([ + // Label::new( + // "You can update your settings or", + // enabled_style.hint.text.clone(), + // ) + // .aligned(), + // Label::new( + // "sign out from the Copilot menu in", + // enabled_style.hint.text.clone(), + // ) + // .aligned(), + // Label::new("the status bar.", enabled_style.hint.text.clone()).aligned(), + // ]) + // .align_children_center() + // .contained() + // .with_style(enabled_style.hint.container), + // ) + // .with_child(theme::ui::cta_button::( + // "Done", + // style.auth.content_width, + // &style.auth.cta_button, + // cx, + // |_, _, cx| cx.remove_window(), + // )) + // .align_children_center() + // .into_any() + // } -// let enabled_style = &style.auth.authorized; -// Flex::column() -// .with_child( -// Label::new("Copilot Enabled!", enabled_style.subheading.text.clone()) -// .contained() -// .with_style(enabled_style.subheading.container) -// .aligned(), -// ) -// .with_child( -// Flex::column() -// .with_children([ -// Label::new( -// "You can update your settings or", -// enabled_style.hint.text.clone(), -// ) -// .aligned(), -// Label::new( -// "sign out from the Copilot menu in", -// enabled_style.hint.text.clone(), -// ) -// .aligned(), -// Label::new("the status bar.", enabled_style.hint.text.clone()).aligned(), -// ]) -// .align_children_center() -// .contained() -// .with_style(enabled_style.hint.container), -// ) -// .with_child(theme::ui::cta_button::( -// "Done", -// style.auth.content_width, -// &style.auth.cta_button, -// cx, -// |_, _, cx| cx.remove_window(), -// )) -// .align_children_center() -// .into_any() -// } + // fn render_unauthorized_modal( + // style: &theme::Copilot, + // cx: &mut ViewContext, + // ) -> AnyElement { + // let unauthorized_style = &style.auth.not_authorized; -// fn render_unauthorized_modal( -// style: &theme::Copilot, -// cx: &mut ViewContext, -// ) -> AnyElement { -// let unauthorized_style = &style.auth.not_authorized; + // Flex::column() + // .with_child( + // Flex::column() + // .with_children([ + // Label::new( + // "Enable Copilot by connecting", + // unauthorized_style.subheading.text.clone(), + // ) + // .aligned(), + // Label::new( + // "your existing license.", + // unauthorized_style.subheading.text.clone(), + // ) + // .aligned(), + // ]) + // .align_children_center() + // .contained() + // .with_style(unauthorized_style.subheading.container), + // ) + // .with_child( + // Flex::column() + // .with_children([ + // Label::new( + // "You must have an active copilot", + // unauthorized_style.warning.text.clone(), + // ) + // .aligned(), + // Label::new( + // "license to use it in Zed.", + // unauthorized_style.warning.text.clone(), + // ) + // .aligned(), + // ]) + // .align_children_center() + // .contained() + // .with_style(unauthorized_style.warning.container), + // ) + // .with_child(theme::ui::cta_button::( + // "Subscribe on GitHub", + // style.auth.content_width, + // &style.auth.cta_button, + // cx, + // |_, _, cx| { + // cx.remove_window(); + // cx.platform().open_url(COPILOT_SIGN_UP_URL) + // }, + // )) + // .align_children_center() + // .into_any() + // } +} -// Flex::column() -// .with_child( -// Flex::column() -// .with_children([ -// Label::new( -// "Enable Copilot by connecting", -// unauthorized_style.subheading.text.clone(), -// ) -// .aligned(), -// Label::new( -// "your existing license.", -// unauthorized_style.subheading.text.clone(), -// ) -// .aligned(), -// ]) -// .align_children_center() -// .contained() -// .with_style(unauthorized_style.subheading.container), -// ) -// .with_child( -// Flex::column() -// .with_children([ -// Label::new( -// "You must have an active copilot", -// unauthorized_style.warning.text.clone(), -// ) -// .aligned(), -// Label::new( -// "license to use it in Zed.", -// unauthorized_style.warning.text.clone(), -// ) -// .aligned(), -// ]) -// .align_children_center() -// .contained() -// .with_style(unauthorized_style.warning.container), -// ) -// .with_child(theme::ui::cta_button::( -// "Subscribe on GitHub", -// style.auth.content_width, -// &style.auth.cta_button, -// cx, -// |_, _, cx| { -// cx.remove_window(); -// cx.platform().open_url(COPILOT_SIGN_UP_URL) -// }, -// )) -// .align_children_center() -// .into_any() -// } -// } +impl Render for CopilotCodeVerification { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + todo!() + } +} // impl Entity for CopilotCodeVerification { // type Event = (); diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 40594a71875f8bf826ef3b47df656db5bd8a7ff7..5ca35844d88ff36b6d230f84a51eb9f4ecc5ff65 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1486,10 +1486,18 @@ impl<'a> WindowContext<'a> { pub fn available_actions(&self) -> Vec> { if let Some(focus_id) = self.window.focus { - self.window + let mut actions = self + .window .current_frame .dispatch_tree - .available_actions(focus_id) + .available_actions(focus_id); + actions.extend( + self.app + .global_action_listeners + .keys() + .filter_map(|type_id| self.app.actions.build_action_type(type_id).ok()), + ); + actions } else { Vec::new() } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 8eee654331a0e0307e9e0cc6e53c043fd77d24fd..7bfec95317307ebbe42969d79298bda21562f9fe 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -101,7 +101,7 @@ pub fn init(cx: &mut AppContext) { // will be initialized as disabled by default, so we filter its commands // out when starting up. cx.update_default_global::(|filter, _| { - filter.filtered_namespaces.insert("vim"); + filter.hidden_namespaces.insert("vim"); }); cx.update_global(|vim: &mut Vim, cx: &mut AppContext| { vim.set_enabled(settings::get::(cx).0, cx) @@ -477,9 +477,9 @@ impl Vim { cx.update_default_global::(|filter, _| { if self.enabled { - filter.filtered_namespaces.remove("vim"); + filter.hidden_namespaces.remove("vim"); } else { - filter.filtered_namespaces.insert("vim"); + filter.hidden_namespaces.insert("vim"); } }); From dffe0ea058a59cbcd0dd99dfb41bbaf6f4511d4f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 5 Dec 2023 09:23:24 -0700 Subject: [PATCH 02/23] Reintroduce menu-related platform callbacks --- crates/gpui2/src/platform.rs | 6 +- crates/gpui2/src/platform/mac/platform.rs | 114 ++++++++++----------- crates/gpui2/src/platform/test/platform.rs | 12 +++ 3 files changed, 73 insertions(+), 59 deletions(-) diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 651392c9c80eef548fb15a3c2d88d33488740661..40c555301bdf00cbad7d2ef36b2c99511daa6d90 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -5,7 +5,7 @@ mod mac; mod test; use crate::{ - point, size, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, + point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, TaskLabel, @@ -90,6 +90,10 @@ pub trait Platform: 'static { fn on_reopen(&self, callback: Box); fn on_event(&self, callback: Box bool>); + fn on_menu_command(&self, callback: Box); + fn on_will_open_menu(&self, callback: Box); + fn on_validate_menu_command(&self, callback: Box bool>); + fn os_name(&self) -> &'static str; fn os_version(&self) -> Result; fn app_version(&self) -> Result; diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 314f055811c57cde9c654294e2d39ed7f1cc3806..9d02c8fb938e9b6f4cd1014e5ebce4f037911bcd 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -1,9 +1,9 @@ use super::BoolExt; use crate::{ - AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, - InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, - PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, - SemanticVersion, VideoTimestamp, WindowOptions, + Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, + ForegroundExecutor, InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, + MacWindow, PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, + Result, SemanticVersion, VideoTimestamp, WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -155,12 +155,12 @@ pub struct MacPlatformState { reopen: Option>, quit: Option>, event: Option bool>>, - // menu_command: Option>, - // validate_menu_command: Option bool>>, + menu_command: Option>, + validate_menu_command: Option bool>>, will_open_menu: Option>, + menu_actions: Vec>, open_urls: Option)>>, finish_launching: Option>, - // menu_actions: Vec>, } impl MacPlatform { @@ -179,12 +179,12 @@ impl MacPlatform { reopen: None, quit: None, event: None, + menu_command: None, + validate_menu_command: None, will_open_menu: None, + menu_actions: Default::default(), open_urls: None, finish_launching: None, - // menu_command: None, - // validate_menu_command: None, - // menu_actions: Default::default(), })) } @@ -681,17 +681,17 @@ impl Platform for MacPlatform { } } - // fn on_menu_command(&self, callback: Box) { - // self.0.lock().menu_command = Some(callback); - // } + fn on_menu_command(&self, callback: Box) { + self.0.lock().menu_command = Some(callback); + } - // fn on_will_open_menu(&self, callback: Box) { - // self.0.lock().will_open_menu = Some(callback); - // } + fn on_will_open_menu(&self, callback: Box) { + self.0.lock().will_open_menu = Some(callback); + } - // fn on_validate_menu_command(&self, callback: Box bool>) { - // self.0.lock().validate_menu_command = Some(callback); - // } + fn on_validate_menu_command(&self, callback: Box bool>) { + self.0.lock().validate_menu_command = Some(callback); + } // fn set_menus(&self, menus: Vec, keystroke_matcher: &KeymapMatcher) { // unsafe { @@ -956,7 +956,7 @@ unsafe fn path_from_objc(path: id) -> PathBuf { PathBuf::from(path) } -unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform { +unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform { let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR); assert!(!platform_ptr.is_null()); &*(platform_ptr as *const MacPlatform) @@ -965,7 +965,7 @@ unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform { extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { unsafe { if let Some(event) = InputEvent::from_native(native_event, None) { - let platform = get_foreground_platform(this); + let platform = get_mac_platform(this); if let Some(callback) = platform.0.lock().event.as_mut() { if !callback(event) { return; @@ -981,7 +981,7 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { let app: id = msg_send![APP_CLASS, sharedApplication]; app.setActivationPolicy_(NSApplicationActivationPolicyRegular); - let platform = get_foreground_platform(this); + let platform = get_mac_platform(this); let callback = platform.0.lock().finish_launching.take(); if let Some(callback) = callback { callback(); @@ -991,7 +991,7 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { if !has_open_windows { - let platform = unsafe { get_foreground_platform(this) }; + let platform = unsafe { get_mac_platform(this) }; if let Some(callback) = platform.0.lock().reopen.as_mut() { callback(); } @@ -999,21 +999,21 @@ extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_wi } extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { - let platform = unsafe { get_foreground_platform(this) }; + let platform = unsafe { get_mac_platform(this) }; if let Some(callback) = platform.0.lock().become_active.as_mut() { callback(); } } extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) { - let platform = unsafe { get_foreground_platform(this) }; + let platform = unsafe { get_mac_platform(this) }; if let Some(callback) = platform.0.lock().resign_active.as_mut() { callback(); } } extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { - let platform = unsafe { get_foreground_platform(this) }; + let platform = unsafe { get_mac_platform(this) }; if let Some(callback) = platform.0.lock().quit.as_mut() { callback(); } @@ -1035,49 +1035,47 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { }) .collect::>() }; - let platform = unsafe { get_foreground_platform(this) }; + let platform = unsafe { get_mac_platform(this) }; if let Some(callback) = platform.0.lock().open_urls.as_mut() { callback(urls); } } -extern "C" fn handle_menu_item(__this: &mut Object, _: Sel, __item: id) { - todo!() - // unsafe { - // let platform = get_foreground_platform(this); - // let mut platform = platform.0.lock(); - // if let Some(mut callback) = platform.menu_command.take() { - // let tag: NSInteger = msg_send![item, tag]; - // let index = tag as usize; - // if let Some(action) = platform.menu_actions.get(index) { - // callback(action.as_ref()); - // } - // platform.menu_command = Some(callback); - // } - // } +extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { + unsafe { + let platform = get_mac_platform(this); + let mut platform = platform.0.lock(); + if let Some(mut callback) = platform.menu_command.take() { + let tag: NSInteger = msg_send![item, tag]; + let index = tag as usize; + if let Some(action) = platform.menu_actions.get(index) { + callback(action.as_ref()); + } + platform.menu_command = Some(callback); + } + } } -extern "C" fn validate_menu_item(__this: &mut Object, _: Sel, __item: id) -> bool { - todo!() - // unsafe { - // let mut result = false; - // let platform = get_foreground_platform(this); - // let mut platform = platform.0.lock(); - // if let Some(mut callback) = platform.validate_menu_command.take() { - // let tag: NSInteger = msg_send![item, tag]; - // let index = tag as usize; - // if let Some(action) = platform.menu_actions.get(index) { - // result = callback(action.as_ref()); - // } - // platform.validate_menu_command = Some(callback); - // } - // result - // } +extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { + unsafe { + let mut result = false; + let platform = get_mac_platform(this); + let mut platform = platform.0.lock(); + if let Some(mut callback) = platform.validate_menu_command.take() { + let tag: NSInteger = msg_send![item, tag]; + let index = tag as usize; + if let Some(action) = platform.menu_actions.get(index) { + result = callback(action.as_ref()); + } + platform.validate_menu_command = Some(callback); + } + result + } } extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) { unsafe { - let platform = get_foreground_platform(this); + let platform = get_mac_platform(this); let mut platform = platform.0.lock(); if let Some(mut callback) = platform.will_open_menu.take() { callback(); diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index fa4b6e18c587521d88d8d4a4fd4041952c584f4a..6fa706f617119bfad728078a378309c16ab19df4 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -205,6 +205,18 @@ impl Platform for TestPlatform { unimplemented!() } + fn on_menu_command(&self, _callback: Box) { + unimplemented!() + } + + fn on_will_open_menu(&self, _callback: Box) { + unimplemented!() + } + + fn on_validate_menu_command(&self, _callback: Box bool>) { + unimplemented!() + } + fn os_name(&self) -> &'static str { "test" } From 631e264e3ccdd9716180e8cbb9479700d1923540 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 5 Dec 2023 13:17:59 -0700 Subject: [PATCH 03/23] Start on app menus --- crates/gpui2/src/app.rs | 40 ++++ crates/gpui2/src/key_dispatch.rs | 20 +- crates/gpui2/src/platform.rs | 4 +- crates/gpui2/src/platform/app_menu.rs | 96 +++++++++ crates/gpui2/src/platform/mac/platform.rs | 230 +++++++++++---------- crates/gpui2/src/platform/mac/window.rs | 2 +- crates/gpui2/src/platform/test/platform.rs | 2 +- crates/gpui2/src/window.rs | 4 +- 8 files changed, 277 insertions(+), 121 deletions(-) create mode 100644 crates/gpui2/src/platform/app_menu.rs diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index fec6f150f6c341f916e0173379aba63bebcc1ffd..8d4dc371e6a3bfd560046db2f562cb9f8ebf991d 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -425,6 +425,10 @@ impl AppContext { .collect() } + pub fn active_window(&self) -> Option { + self.platform.active_window() + } + /// Opens a new window with the given option and the root view returned by the given function. /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific /// functionality. @@ -1015,6 +1019,42 @@ impl AppContext { activate(); subscription } + + pub(crate) fn clear_pending_keystrokes(&mut self) { + for window in self.windows() { + window + .update(self, |_, cx| { + cx.window + .current_frame + .dispatch_tree + .clear_pending_keystrokes() + }) + .ok(); + } + } + + pub fn is_action_available(&mut self, action: &dyn Action) -> bool { + if let Some(window) = self.active_window() { + let window_action_available = window + .update(self, |_, cx| { + if let Some(focus_id) = cx.window.focus { + cx.window + .current_frame + .dispatch_tree + .is_action_available(action, focus_id) + } else { + false + } + }) + .unwrap_or(false); + if window_action_available { + return true; + } + } + + self.global_action_listeners + .contains_key(&action.as_any().type_id()) + } } impl Context for AppContext { diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 4838b1a612ce65ba33c03ac25da878a752f716d3..0df052dfdf9e0f066666f56a94295d803dfab613 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -82,13 +82,13 @@ impl DispatchTree { } } - pub fn clear_keystroke_matchers(&mut self) { + pub fn clear_pending_keystrokes(&mut self) { self.keystroke_matchers.clear(); } /// Preserve keystroke matchers from previous frames to support multi-stroke /// bindings across multiple frames. - pub fn preserve_keystroke_matchers(&mut self, old_tree: &mut Self, focus_id: Option) { + pub fn preserve_pending_keystrokes(&mut self, old_tree: &mut Self, focus_id: Option) { if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) { let dispatch_path = self.dispatch_path(node_id); @@ -163,6 +163,22 @@ impl DispatchTree { actions } + pub fn is_action_available(&self, action: &dyn Action, target: FocusId) -> bool { + if let Some(node) = self.focusable_node_ids.get(&target) { + for node_id in self.dispatch_path(*node) { + let node = &self.nodes[node_id.0]; + if node + .action_listeners + .iter() + .any(|listener| listener.action_type == action.as_any().type_id()) + { + return true; + } + } + } + false + } + pub fn bindings_for_action( &self, action: &dyn Action, diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 40c555301bdf00cbad7d2ef36b2c99511daa6d90..5d3a92f052eb8eafebe7e93b2d47e82c586c270a 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -1,3 +1,4 @@ +mod app_menu; mod keystroke; #[cfg(target_os = "macos")] mod mac; @@ -32,6 +33,7 @@ use std::{ }; use uuid::Uuid; +pub use app_menu::*; pub use keystroke::*; #[cfg(target_os = "macos")] pub use mac::*; @@ -59,7 +61,7 @@ pub trait Platform: 'static { fn displays(&self) -> Vec>; fn display(&self, id: DisplayId) -> Option>; - fn main_window(&self) -> Option; + fn active_window(&self) -> Option; fn open_window( &self, handle: AnyWindowHandle, diff --git a/crates/gpui2/src/platform/app_menu.rs b/crates/gpui2/src/platform/app_menu.rs new file mode 100644 index 0000000000000000000000000000000000000000..0f784f6585f9233c953bad50dbd6d6a2bc3207f9 --- /dev/null +++ b/crates/gpui2/src/platform/app_menu.rs @@ -0,0 +1,96 @@ +use crate::{Action, AppContext, Platform}; +use util::ResultExt; + +pub struct Menu<'a> { + pub name: &'a str, + pub items: Vec>, +} + +pub enum MenuItem<'a> { + Separator, + Submenu(Menu<'a>), + Action { + name: &'a str, + action: Box, + os_action: Option, + }, +} + +impl<'a> MenuItem<'a> { + pub fn separator() -> Self { + Self::Separator + } + + pub fn submenu(menu: Menu<'a>) -> Self { + Self::Submenu(menu) + } + + pub fn action(name: &'a str, action: impl Action) -> Self { + Self::Action { + name, + action: Box::new(action), + os_action: None, + } + } + + pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self { + Self::Action { + name, + action: Box::new(action), + os_action: Some(os_action), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum OsAction { + Cut, + Copy, + Paste, + SelectAll, + Undo, + Redo, +} + +pub(crate) fn init(platform: &dyn Platform, cx: &mut AppContext) { + platform.on_will_open_menu(Box::new({ + let cx = cx.to_async(); + move || { + cx.update(|cx| cx.clear_pending_keystrokes()).ok(); + } + })); + + platform.on_validate_menu_command(Box::new({ + let cx = cx.to_async(); + move |action| { + cx.update(|cx| cx.is_action_available(action)) + .unwrap_or(false) + } + })); + + platform.on_menu_command(Box::new({ + let cx = cx.to_async(); + move |action| { + cx.update(|cx| { + // if let Some(main_window) = cx.active_window() { + // let dispatched = main_window + // .update(&mut *cx, |cx| { + // if let Some(view_id) = cx.focused_view_id() { + // cx.dispatch_action(Some(view_id), action); + // true + // } else { + // false + // } + // }) + // .unwrap_or(false); + + // if dispatched { + // return; + // } + // } + // cx.dispatch_global_action_any(action); + }) + .log_err(); + } + })); +} diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 9d02c8fb938e9b6f4cd1014e5ebce4f037911bcd..6dae0afc0e59af2abaeba861bb6c22638d3b0847 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -1,16 +1,17 @@ use super::BoolExt; use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, - ForegroundExecutor, InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, - MacWindow, PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, - Result, SemanticVersion, VideoTimestamp, WindowOptions, + ForegroundExecutor, InputEvent, KeystrokeMatcher, MacDispatcher, MacDisplay, MacDisplayLinker, + MacTextSystem, MacWindow, MenuItem, PathPromptOptions, Platform, PlatformDisplay, + PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; use cocoa::{ appkit::{ NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, - NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, NSSavePanel, NSWindow, + NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, + NSSavePanel, NSWindow, }, base::{id, nil, BOOL, YES}, foundation::{ @@ -237,114 +238,115 @@ impl MacPlatform { // application_menu // } - // unsafe fn create_menu_item( - // &self, - // item: MenuItem, - // delegate: id, - // actions: &mut Vec>, - // keystroke_matcher: &KeymapMatcher, - // ) -> id { - // match item { - // MenuItem::Separator => NSMenuItem::separatorItem(nil), - // MenuItem::Action { - // name, - // action, - // os_action, - // } => { - // // TODO - // let keystrokes = keystroke_matcher - // .bindings_for_action(action.id()) - // .find(|binding| binding.action().eq(action.as_ref())) - // .map(|binding| binding.keystrokes()); - // let selector = match os_action { - // Some(crate::OsAction::Cut) => selector("cut:"), - // Some(crate::OsAction::Copy) => selector("copy:"), - // Some(crate::OsAction::Paste) => selector("paste:"), - // Some(crate::OsAction::SelectAll) => selector("selectAll:"), - // Some(crate::OsAction::Undo) => selector("undo:"), - // Some(crate::OsAction::Redo) => selector("redo:"), - // None => selector("handleGPUIMenuItem:"), - // }; - - // let item; - // if let Some(keystrokes) = keystrokes { - // if keystrokes.len() == 1 { - // let keystroke = &keystrokes[0]; - // let mut mask = NSEventModifierFlags::empty(); - // for (modifier, flag) in &[ - // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), - // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), - // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), - // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask), - // ] { - // if *modifier { - // mask |= *flag; - // } - // } - - // item = NSMenuItem::alloc(nil) - // .initWithTitle_action_keyEquivalent_( - // ns_string(name), - // selector, - // ns_string(key_to_native(&keystroke.key).as_ref()), - // ) - // .autorelease(); - // item.setKeyEquivalentModifierMask_(mask); - // } - // // For multi-keystroke bindings, render the keystroke as part of the title. - // else { - // use std::fmt::Write; - - // let mut name = format!("{name} ["); - // for (i, keystroke) in keystrokes.iter().enumerate() { - // if i > 0 { - // name.push(' '); - // } - // write!(&mut name, "{}", keystroke).unwrap(); - // } - // name.push(']'); - - // item = NSMenuItem::alloc(nil) - // .initWithTitle_action_keyEquivalent_( - // ns_string(&name), - // selector, - // ns_string(""), - // ) - // .autorelease(); - // } - // } else { - // item = NSMenuItem::alloc(nil) - // .initWithTitle_action_keyEquivalent_( - // ns_string(name), - // selector, - // ns_string(""), - // ) - // .autorelease(); - // } - - // let tag = actions.len() as NSInteger; - // let _: () = msg_send![item, setTag: tag]; - // actions.push(action); - // item - // } - // MenuItem::Submenu(Menu { name, items }) => { - // let item = NSMenuItem::new(nil).autorelease(); - // let submenu = NSMenu::new(nil).autorelease(); - // submenu.setDelegate_(delegate); - // for item in items { - // submenu.addItem_(self.create_menu_item( - // item, - // delegate, - // actions, - // keystroke_matcher, - // )); - // } - // item.setSubmenu_(submenu); - // item.setTitle_(ns_string(name)); - // item - // } - // } - // } + unsafe fn create_menu_item( + &self, + item: MenuItem, + delegate: id, + actions: &mut Vec>, + keystroke_matcher: &KeystrokeMatcher, + ) -> id { + todo!() + // match item { + // MenuItem::Separator => NSMenuItem::separatorItem(nil), + // MenuItem::Action { + // name, + // action, + // os_action, + // } => { + // // TODO + // let keystrokes = keystroke_matcher + // .bindings_for_action(action.id()) + // .find(|binding| binding.action().eq(action.as_ref())) + // .map(|binding| binding.keystrokes()); + // let selector = match os_action { + // Some(crate::OsAction::Cut) => selector("cut:"), + // Some(crate::OsAction::Copy) => selector("copy:"), + // Some(crate::OsAction::Paste) => selector("paste:"), + // Some(crate::OsAction::SelectAll) => selector("selectAll:"), + // Some(crate::OsAction::Undo) => selector("undo:"), + // Some(crate::OsAction::Redo) => selector("redo:"), + // None => selector("handleGPUIMenuItem:"), + // }; + + // let item; + // if let Some(keystrokes) = keystrokes { + // if keystrokes.len() == 1 { + // let keystroke = &keystrokes[0]; + // let mut mask = NSEventModifierFlags::empty(); + // for (modifier, flag) in &[ + // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), + // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), + // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), + // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask), + // ] { + // if *modifier { + // mask |= *flag; + // } + // } + + // item = NSMenuItem::alloc(nil) + // .initWithTitle_action_keyEquivalent_( + // ns_string(name), + // selector, + // ns_string(key_to_native(&keystroke.key).as_ref()), + // ) + // .autorelease(); + // item.setKeyEquivalentModifierMask_(mask); + // } + // // For multi-keystroke bindings, render the keystroke as part of the title. + // else { + // use std::fmt::Write; + + // let mut name = format!("{name} ["); + // for (i, keystroke) in keystrokes.iter().enumerate() { + // if i > 0 { + // name.push(' '); + // } + // write!(&mut name, "{}", keystroke).unwrap(); + // } + // name.push(']'); + + // item = NSMenuItem::alloc(nil) + // .initWithTitle_action_keyEquivalent_( + // ns_string(&name), + // selector, + // ns_string(""), + // ) + // .autorelease(); + // } + // } else { + // item = NSMenuItem::alloc(nil) + // .initWithTitle_action_keyEquivalent_( + // ns_string(name), + // selector, + // ns_string(""), + // ) + // .autorelease(); + // } + + // let tag = actions.len() as NSInteger; + // let _: () = msg_send![item, setTag: tag]; + // actions.push(action); + // item + // } + // MenuItem::Submenu(Menu { name, items }) => { + // let item = NSMenuItem::new(nil).autorelease(); + // let submenu = NSMenu::new(nil).autorelease(); + // submenu.setDelegate_(delegate); + // for item in items { + // submenu.addItem_(self.create_menu_item( + // item, + // delegate, + // actions, + // keystroke_matcher, + // )); + // } + // item.setSubmenu_(submenu); + // item.setTitle_(ns_string(name)); + // item + // } + // } + } } impl Platform for MacPlatform { @@ -479,8 +481,8 @@ impl Platform for MacPlatform { MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) } - fn main_window(&self) -> Option { - MacWindow::main_window() + fn active_window(&self) -> Option { + MacWindow::active_window() } fn open_window( diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index 5b72c10851ff555b08669d8db96e143509e8ad46..ba9a67e1580c16dfdbfc0849d8d1678af308c96a 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/crates/gpui2/src/platform/mac/window.rs @@ -662,7 +662,7 @@ impl MacWindow { } } - pub fn main_window() -> Option { + pub fn active_window() -> Option { unsafe { let app = NSApplication::sharedApplication(nil); let main_window: id = msg_send![app, mainWindow]; diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 6fa706f617119bfad728078a378309c16ab19df4..264273730510365d319120e3faaa0efd35076a1d 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -127,7 +127,7 @@ impl Platform for TestPlatform { self.displays().iter().find(|d| d.id() == id).cloned() } - fn main_window(&self) -> Option { + fn active_window(&self) -> Option { unimplemented!() } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 8eb14769bf1e45e30a468e9f32d694201591be86..8645554e5af83600b6ff6438869048fee09b46d1 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -430,7 +430,7 @@ impl<'a> WindowContext<'a> { self.window .current_frame .dispatch_tree - .clear_keystroke_matchers(); + .clear_pending_keystrokes(); self.app.push_effect(Effect::FocusChanged { window_handle: self.window.handle, focused: Some(focus_id), @@ -1177,7 +1177,7 @@ impl<'a> WindowContext<'a> { self.window .current_frame .dispatch_tree - .preserve_keystroke_matchers( + .preserve_pending_keystrokes( &mut self.window.previous_frame.dispatch_tree, self.window.focus, ); From 79567d1c87cf9cd7c46fa3cc92561dfd3911204e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 5 Dec 2023 15:49:06 -0700 Subject: [PATCH 04/23] Add AppContext::dispatch_action and use it for app menu actions Co-Authored-By: Marshall Co-Authored-By: Julia --- crates/gpui2/src/app.rs | 61 +++++++++++++++++++++- crates/gpui2/src/platform.rs | 6 +-- crates/gpui2/src/platform/app_menu.rs | 27 ++-------- crates/gpui2/src/platform/mac/platform.rs | 6 +-- crates/gpui2/src/platform/test/platform.rs | 6 +-- 5 files changed, 73 insertions(+), 33 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 8d4dc371e6a3bfd560046db2f562cb9f8ebf991d..74712feb287468ef67917235b544ffd85ecf8400 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -39,7 +39,10 @@ use std::{ sync::{atomic::Ordering::SeqCst, Arc}, time::Duration, }; -use util::http::{self, HttpClient}; +use util::{ + http::{self, HttpClient}, + ResultExt, +}; /// Temporary(?) wrapper around RefCell to help us debug any double borrows. /// Strongly consider removing after stabilization. @@ -1055,6 +1058,62 @@ impl AppContext { self.global_action_listeners .contains_key(&action.as_any().type_id()) } + + pub fn dispatch_action(&mut self, action: &dyn Action) { + self.propagate_event = true; + + if let Some(mut global_listeners) = self + .global_action_listeners + .remove(&action.as_any().type_id()) + { + for listener in &global_listeners { + listener(action, DispatchPhase::Capture, self); + if !self.propagate_event { + break; + } + } + + global_listeners.extend( + self.global_action_listeners + .remove(&action.as_any().type_id()) + .unwrap_or_default(), + ); + + self.global_action_listeners + .insert(action.as_any().type_id(), global_listeners); + } + + if self.propagate_event { + if let Some(active_window) = self.active_window() { + active_window + .update(self, |_, cx| cx.dispatch_action(action.boxed_clone())) + .log_err(); + } + } + + if self.propagate_event { + if let Some(mut global_listeners) = self + .global_action_listeners + .remove(&action.as_any().type_id()) + { + for listener in global_listeners.iter().rev() { + listener(action, DispatchPhase::Bubble, self); + if !self.propagate_event { + break; + } + } + + global_listeners.extend( + self.global_action_listeners + .remove(&action.as_any().type_id()) + .unwrap_or_default(), + ); + + self.global_action_listeners + .insert(action.as_any().type_id(), global_listeners); + } + } + } } impl Context for AppContext { diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 5d3a92f052eb8eafebe7e93b2d47e82c586c270a..96be670af7dbc92af50571ab228dcaa663f90a75 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -92,9 +92,9 @@ pub trait Platform: 'static { fn on_reopen(&self, callback: Box); fn on_event(&self, callback: Box bool>); - fn on_menu_command(&self, callback: Box); - fn on_will_open_menu(&self, callback: Box); - fn on_validate_menu_command(&self, callback: Box bool>); + fn on_app_menu_action(&self, callback: Box); + fn on_will_open_app_menu(&self, callback: Box); + fn on_validate_app_menu_command(&self, callback: Box bool>); fn os_name(&self) -> &'static str; fn os_version(&self) -> Result; diff --git a/crates/gpui2/src/platform/app_menu.rs b/crates/gpui2/src/platform/app_menu.rs index 0f784f6585f9233c953bad50dbd6d6a2bc3207f9..bfac1366b286ce58f1c423a2da13157881e7c3c8 100644 --- a/crates/gpui2/src/platform/app_menu.rs +++ b/crates/gpui2/src/platform/app_menu.rs @@ -53,14 +53,14 @@ pub enum OsAction { } pub(crate) fn init(platform: &dyn Platform, cx: &mut AppContext) { - platform.on_will_open_menu(Box::new({ + platform.on_will_open_app_menu(Box::new({ let cx = cx.to_async(); move || { cx.update(|cx| cx.clear_pending_keystrokes()).ok(); } })); - platform.on_validate_menu_command(Box::new({ + platform.on_validate_app_menu_command(Box::new({ let cx = cx.to_async(); move |action| { cx.update(|cx| cx.is_action_available(action)) @@ -68,29 +68,10 @@ pub(crate) fn init(platform: &dyn Platform, cx: &mut AppContext) { } })); - platform.on_menu_command(Box::new({ + platform.on_app_menu_action(Box::new({ let cx = cx.to_async(); move |action| { - cx.update(|cx| { - // if let Some(main_window) = cx.active_window() { - // let dispatched = main_window - // .update(&mut *cx, |cx| { - // if let Some(view_id) = cx.focused_view_id() { - // cx.dispatch_action(Some(view_id), action); - // true - // } else { - // false - // } - // }) - // .unwrap_or(false); - - // if dispatched { - // return; - // } - // } - // cx.dispatch_global_action_any(action); - }) - .log_err(); + cx.update(|cx| cx.dispatch_action(action)).log_err(); } })); } diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 6dae0afc0e59af2abaeba861bb6c22638d3b0847..d7fc37f0def0736d189a891c733ed8e09997be49 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -683,15 +683,15 @@ impl Platform for MacPlatform { } } - fn on_menu_command(&self, callback: Box) { + fn on_app_menu_action(&self, callback: Box) { self.0.lock().menu_command = Some(callback); } - fn on_will_open_menu(&self, callback: Box) { + fn on_will_open_app_menu(&self, callback: Box) { self.0.lock().will_open_menu = Some(callback); } - fn on_validate_menu_command(&self, callback: Box bool>) { + fn on_validate_app_menu_command(&self, callback: Box bool>) { self.0.lock().validate_menu_command = Some(callback); } diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 264273730510365d319120e3faaa0efd35076a1d..952a9a96cac98e85863931f3e726d71b85e58896 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -205,15 +205,15 @@ impl Platform for TestPlatform { unimplemented!() } - fn on_menu_command(&self, _callback: Box) { + fn on_app_menu_action(&self, _callback: Box) { unimplemented!() } - fn on_will_open_menu(&self, _callback: Box) { + fn on_will_open_app_menu(&self, _callback: Box) { unimplemented!() } - fn on_validate_menu_command(&self, _callback: Box bool>) { + fn on_validate_app_menu_command(&self, _callback: Box bool>) { unimplemented!() } From 82534b66125286c6355cdb40c19fd698471e8b52 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 5 Dec 2023 16:37:01 -0700 Subject: [PATCH 05/23] Get app menus basically working - Everything is still disabled when there is no active window. Co-Authored-By: Marshall --- crates/editor2/src/display_map/block_map.rs | 8 +- crates/editor2/src/display_map/wrap_map.rs | 2 +- crates/editor2/src/editor_tests.rs | 6 +- crates/gpui2/src/app.rs | 21 +- crates/gpui2/src/app/test_context.rs | 24 +- crates/gpui2/src/platform.rs | 11 +- crates/gpui2/src/platform/app_menu.rs | 2 +- crates/gpui2/src/platform/mac/platform.rs | 341 ++++++++++---------- crates/gpui2/src/platform/test/platform.rs | 9 +- crates/settings2/src/settings_file.rs | 36 --- crates/zed2/src/app_menus.rs | 175 ++++++++++ crates/zed2/src/main.rs | 10 +- crates/zed2/src/zed2.rs | 41 ++- 13 files changed, 447 insertions(+), 239 deletions(-) create mode 100644 crates/zed2/src/app_menus.rs diff --git a/crates/editor2/src/display_map/block_map.rs b/crates/editor2/src/display_map/block_map.rs index 64e46549fd6c7b9ae2576ca68d7c0f2af52b750e..cc0095bca94f30f6d65866c640092cb384d2cce3 100644 --- a/crates/editor2/src/display_map/block_map.rs +++ b/crates/editor2/src/display_map/block_map.rs @@ -993,7 +993,7 @@ mod tests { use super::*; use crate::display_map::inlay_map::InlayMap; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; - use gpui::{div, font, px, Element, Platform as _}; + use gpui::{div, font, px, Element}; use multi_buffer::MultiBuffer; use rand::prelude::*; use settings::SettingsStore; @@ -1185,11 +1185,7 @@ mod tests { fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) { cx.update(|cx| init_test(cx)); - let font_id = cx - .test_platform - .text_system() - .font_id(&font("Helvetica")) - .unwrap(); + let font_id = cx.text_system().font_id(&font("Helvetica")).unwrap(); let text = "one two three\nfour five six\nseven eight"; diff --git a/crates/editor2/src/display_map/wrap_map.rs b/crates/editor2/src/display_map/wrap_map.rs index a2ac0ec849bfb9b26983c897a2ae3cc2ebd9878c..ca9db7754baec0b1477c19d0f8efaa98fd8f14c3 100644 --- a/crates/editor2/src/display_map/wrap_map.rs +++ b/crates/editor2/src/display_map/wrap_map.rs @@ -1032,7 +1032,7 @@ mod tests { display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, MultiBuffer, }; - use gpui::{font, px, test::observe, Platform}; + use gpui::{font, px, test::observe}; use rand::prelude::*; use settings::SettingsStore; use smol::stream::StreamExt; diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs index 424da8987eb6d673f0e789d4b8ae8b1620967045..b5f156f494936cba312d9b44365d1a903a803c4e 100644 --- a/crates/editor2/src/editor_tests.rs +++ b/crates/editor2/src/editor_tests.rs @@ -12,7 +12,7 @@ use futures::StreamExt; use gpui::{ div, serde_json::{self, json}, - Div, Flatten, Platform, TestAppContext, VisualTestContext, WindowBounds, WindowOptions, + Div, Flatten, TestAppContext, VisualTestContext, WindowBounds, WindowOptions, }; use indoc::indoc; use language::{ @@ -3238,9 +3238,7 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) { the lazy dog"}); cx.update_editor(|e, cx| e.copy(&Copy, cx)); assert_eq!( - cx.test_platform - .read_from_clipboard() - .map(|item| item.text().to_owned()), + cx.read_from_clipboard().map(|item| item.text().to_owned()), Some("fox jumps over\n".to_owned()) ); diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 74712feb287468ef67917235b544ffd85ecf8400..79beca75da7f89e83ff2dcd9079f45c29ff153e2 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -15,10 +15,10 @@ use smol::future::FutureExt; pub use test_context::*; use crate::{ - current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView, - AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, + current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any, + AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, - ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform, + ForegroundExecutor, KeyBinding, Keymap, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, @@ -278,6 +278,8 @@ impl AppContext { }), }); + init_app_menus(platform.as_ref(), &mut *app.borrow_mut()); + platform.on_quit(Box::new({ let cx = app.clone(); move || { @@ -1059,6 +1061,19 @@ impl AppContext { .contains_key(&action.as_any().type_id()) } + pub fn set_menus(&mut self, menus: Vec) { + if let Some(active_window) = self.active_window() { + active_window + .update(self, |_, cx| { + cx.platform + .set_menus(menus, Some(&cx.window.current_frame.dispatch_tree)); + }) + .ok(); + } else { + self.platform.set_menus(menus, None); + } + } + pub fn dispatch_action(&mut self, action: &dyn Action) { self.propagate_event = true; diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index a9403de9bce63a62dfedf455d997de34dff4d856..cbd70e52ffab4d539abfe6a177f8a112825b8402 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,9 +1,10 @@ use crate::{ div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, - BackgroundExecutor, Bounds, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent, - KeyDownEvent, Keystroke, Model, ModelContext, Pixels, PlatformWindow, Point, Render, Result, - Size, Task, TestDispatcher, TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, - VisualContext, WindowBounds, WindowContext, WindowHandle, WindowOptions, + BackgroundExecutor, Bounds, ClipboardItem, Context, Div, Entity, EventEmitter, + ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform, + PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, + TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext, + WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -16,6 +17,7 @@ pub struct TestAppContext { pub foreground_executor: ForegroundExecutor, pub dispatcher: TestDispatcher, pub test_platform: Rc, + text_system: Arc, } impl Context for TestAppContext { @@ -82,6 +84,7 @@ impl TestAppContext { let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone()); let asset_source = Arc::new(()); let http_client = util::http::FakeHttpClient::with_404_response(); + let text_system = Arc::new(TextSystem::new(platform.text_system())); Self { app: AppContext::new(platform.clone(), asset_source, http_client), @@ -89,6 +92,7 @@ impl TestAppContext { foreground_executor, dispatcher: dispatcher.clone(), test_platform: platform, + text_system, } } @@ -155,6 +159,18 @@ impl TestAppContext { (view, Box::leak(cx)) } + pub fn text_system(&self) -> &Arc { + &self.text_system + } + + pub fn write_to_clipboard(&self, item: ClipboardItem) { + self.test_platform.write_to_clipboard(item) + } + + pub fn read_from_clipboard(&self) -> Option { + self.test_platform.read_from_clipboard() + } + pub fn simulate_new_path_selection( &self, select_path: impl FnOnce(&std::path::Path) -> Option, diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 96be670af7dbc92af50571ab228dcaa663f90a75..7bcd91a5e07dbc03a52fedd6837b9e06c542c6f4 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -6,10 +6,10 @@ mod mac; mod test; use crate::{ - point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, - FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout, - Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, - SharedString, Size, TaskLabel, + point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, DispatchTree, + Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, + LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, + Scene, SharedString, Size, TaskLabel, }; use anyhow::{anyhow, bail}; use async_task::Runnable; @@ -46,7 +46,7 @@ pub(crate) fn current_platform() -> Rc { Rc::new(MacPlatform::new()) } -pub trait Platform: 'static { +pub(crate) trait Platform: 'static { fn background_executor(&self) -> BackgroundExecutor; fn foreground_executor(&self) -> ForegroundExecutor; fn text_system(&self) -> Arc; @@ -92,6 +92,7 @@ pub trait Platform: 'static { fn on_reopen(&self, callback: Box); fn on_event(&self, callback: Box bool>); + fn set_menus(&self, menus: Vec, dispatch_tree: Option<&DispatchTree>); fn on_app_menu_action(&self, callback: Box); fn on_will_open_app_menu(&self, callback: Box); fn on_validate_app_menu_command(&self, callback: Box bool>); diff --git a/crates/gpui2/src/platform/app_menu.rs b/crates/gpui2/src/platform/app_menu.rs index bfac1366b286ce58f1c423a2da13157881e7c3c8..10fe2cf33ae6d8b830efc92f72bba7f5157de4b9 100644 --- a/crates/gpui2/src/platform/app_menu.rs +++ b/crates/gpui2/src/platform/app_menu.rs @@ -52,7 +52,7 @@ pub enum OsAction { Redo, } -pub(crate) fn init(platform: &dyn Platform, cx: &mut AppContext) { +pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &mut AppContext) { platform.on_will_open_app_menu(Box::new({ let cx = cx.to_async(); move || { diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index d7fc37f0def0736d189a891c733ed8e09997be49..8a5ee676f7c9e76364e6aea27a9b064a917cca36 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -1,8 +1,8 @@ -use super::BoolExt; +use super::{events::key_to_native, BoolExt}; use crate::{ - Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, - ForegroundExecutor, InputEvent, KeystrokeMatcher, MacDispatcher, MacDisplay, MacDisplayLinker, - MacTextSystem, MacWindow, MenuItem, PathPromptOptions, Platform, PlatformDisplay, + Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DispatchTree, + DisplayId, ForegroundExecutor, InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, + MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions, }; use anyhow::anyhow; @@ -10,10 +10,10 @@ use block::ConcreteBlock; use cocoa::{ appkit::{ NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, - NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, - NSSavePanel, NSWindow, + NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, + NSPasteboardTypeString, NSSavePanel, NSWindow, }, - base::{id, nil, BOOL, YES}, + base::{id, nil, selector, BOOL, YES}, foundation::{ NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString, NSUInteger, NSURL, @@ -201,151 +201,155 @@ impl MacPlatform { } } - // unsafe fn create_menu_bar( - // &self, - // menus: Vec, - // delegate: id, - // actions: &mut Vec>, - // keystroke_matcher: &KeymapMatcher, - // ) -> id { - // let application_menu = NSMenu::new(nil).autorelease(); - // application_menu.setDelegate_(delegate); - - // for menu_config in menus { - // let menu = NSMenu::new(nil).autorelease(); - // menu.setTitle_(ns_string(menu_config.name)); - // menu.setDelegate_(delegate); - - // for item_config in menu_config.items { - // menu.addItem_(self.create_menu_item( - // item_config, - // delegate, - // actions, - // keystroke_matcher, - // )); - // } - - // let menu_item = NSMenuItem::new(nil).autorelease(); - // menu_item.setSubmenu_(menu); - // application_menu.addItem_(menu_item); - - // if menu_config.name == "Window" { - // let app: id = msg_send![APP_CLASS, sharedApplication]; - // app.setWindowsMenu_(menu); - // } - // } - - // application_menu - // } + unsafe fn create_menu_bar( + &self, + menus: Vec, + delegate: id, + actions: &mut Vec>, + dispatch_tree: Option<&DispatchTree>, + ) -> id { + let application_menu = NSMenu::new(nil).autorelease(); + application_menu.setDelegate_(delegate); + + for menu_config in menus { + let menu = NSMenu::new(nil).autorelease(); + menu.setTitle_(ns_string(menu_config.name)); + menu.setDelegate_(delegate); + + for item_config in menu_config.items { + menu.addItem_(self.create_menu_item(item_config, delegate, actions, dispatch_tree)); + } + + let menu_item = NSMenuItem::new(nil).autorelease(); + menu_item.setSubmenu_(menu); + application_menu.addItem_(menu_item); + + if menu_config.name == "Window" { + let app: id = msg_send![APP_CLASS, sharedApplication]; + app.setWindowsMenu_(menu); + } + } + + application_menu + } unsafe fn create_menu_item( &self, item: MenuItem, delegate: id, actions: &mut Vec>, - keystroke_matcher: &KeystrokeMatcher, + dispatch_tree: Option<&DispatchTree>, ) -> id { - todo!() - // match item { - // MenuItem::Separator => NSMenuItem::separatorItem(nil), - // MenuItem::Action { - // name, - // action, - // os_action, - // } => { - // // TODO - // let keystrokes = keystroke_matcher - // .bindings_for_action(action.id()) - // .find(|binding| binding.action().eq(action.as_ref())) - // .map(|binding| binding.keystrokes()); - // let selector = match os_action { - // Some(crate::OsAction::Cut) => selector("cut:"), - // Some(crate::OsAction::Copy) => selector("copy:"), - // Some(crate::OsAction::Paste) => selector("paste:"), - // Some(crate::OsAction::SelectAll) => selector("selectAll:"), - // Some(crate::OsAction::Undo) => selector("undo:"), - // Some(crate::OsAction::Redo) => selector("redo:"), - // None => selector("handleGPUIMenuItem:"), - // }; - - // let item; - // if let Some(keystrokes) = keystrokes { - // if keystrokes.len() == 1 { - // let keystroke = &keystrokes[0]; - // let mut mask = NSEventModifierFlags::empty(); - // for (modifier, flag) in &[ - // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), - // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), - // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), - // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask), - // ] { - // if *modifier { - // mask |= *flag; - // } - // } - - // item = NSMenuItem::alloc(nil) - // .initWithTitle_action_keyEquivalent_( - // ns_string(name), - // selector, - // ns_string(key_to_native(&keystroke.key).as_ref()), - // ) - // .autorelease(); - // item.setKeyEquivalentModifierMask_(mask); - // } - // // For multi-keystroke bindings, render the keystroke as part of the title. - // else { - // use std::fmt::Write; - - // let mut name = format!("{name} ["); - // for (i, keystroke) in keystrokes.iter().enumerate() { - // if i > 0 { - // name.push(' '); - // } - // write!(&mut name, "{}", keystroke).unwrap(); - // } - // name.push(']'); - - // item = NSMenuItem::alloc(nil) - // .initWithTitle_action_keyEquivalent_( - // ns_string(&name), - // selector, - // ns_string(""), - // ) - // .autorelease(); - // } - // } else { - // item = NSMenuItem::alloc(nil) - // .initWithTitle_action_keyEquivalent_( - // ns_string(name), - // selector, - // ns_string(""), - // ) - // .autorelease(); - // } - - // let tag = actions.len() as NSInteger; - // let _: () = msg_send![item, setTag: tag]; - // actions.push(action); - // item - // } - // MenuItem::Submenu(Menu { name, items }) => { - // let item = NSMenuItem::new(nil).autorelease(); - // let submenu = NSMenu::new(nil).autorelease(); - // submenu.setDelegate_(delegate); - // for item in items { - // submenu.addItem_(self.create_menu_item( - // item, - // delegate, - // actions, - // keystroke_matcher, - // )); - // } - // item.setSubmenu_(submenu); - // item.setTitle_(ns_string(name)); - // item - // } - // } + match item { + MenuItem::Separator => NSMenuItem::separatorItem(nil), + MenuItem::Action { + name, + action, + os_action, + } => { + let bindings = dispatch_tree + .map(|tree| tree.bindings_for_action(action.as_ref(), &tree.context_stack)) + .unwrap_or_default(); + let keystrokes = bindings + .iter() + .find(|binding| binding.action().partial_eq(action.as_ref())) + .map(|binding| binding.keystrokes()); + + let selector = match os_action { + Some(crate::OsAction::Cut) => selector("cut:"), + Some(crate::OsAction::Copy) => selector("copy:"), + Some(crate::OsAction::Paste) => selector("paste:"), + Some(crate::OsAction::SelectAll) => selector("selectAll:"), + Some(crate::OsAction::Undo) => selector("undo:"), + Some(crate::OsAction::Redo) => selector("redo:"), + None => selector("handleGPUIMenuItem:"), + }; + + let item; + if let Some(keystrokes) = keystrokes { + if keystrokes.len() == 1 { + let keystroke = &keystrokes[0]; + let mut mask = NSEventModifierFlags::empty(); + for (modifier, flag) in &[ + ( + keystroke.modifiers.command, + NSEventModifierFlags::NSCommandKeyMask, + ), + ( + keystroke.modifiers.control, + NSEventModifierFlags::NSControlKeyMask, + ), + ( + keystroke.modifiers.alt, + NSEventModifierFlags::NSAlternateKeyMask, + ), + ( + keystroke.modifiers.shift, + NSEventModifierFlags::NSShiftKeyMask, + ), + ] { + if *modifier { + mask |= *flag; + } + } + + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(name), + selector, + ns_string(key_to_native(&keystroke.key).as_ref()), + ) + .autorelease(); + item.setKeyEquivalentModifierMask_(mask); + } + // For multi-keystroke bindings, render the keystroke as part of the title. + else { + use std::fmt::Write; + + let mut name = format!("{name} ["); + for (i, keystroke) in keystrokes.iter().enumerate() { + if i > 0 { + name.push(' '); + } + write!(&mut name, "{}", keystroke).unwrap(); + } + name.push(']'); + + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(&name), + selector, + ns_string(""), + ) + .autorelease(); + } + } else { + item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_( + ns_string(name), + selector, + ns_string(""), + ) + .autorelease(); + } + + let tag = actions.len() as NSInteger; + let _: () = msg_send![item, setTag: tag]; + actions.push(action); + item + } + MenuItem::Submenu(Menu { name, items }) => { + let item = NSMenuItem::new(nil).autorelease(); + let submenu = NSMenu::new(nil).autorelease(); + submenu.setDelegate_(delegate); + for item in items { + submenu.addItem_(self.create_menu_item(item, delegate, actions, dispatch_tree)); + } + item.setSubmenu_(submenu); + item.setTitle_(ns_string(name)); + item + } + } } } @@ -633,6 +637,18 @@ impl Platform for MacPlatform { self.0.lock().event = Some(callback); } + fn on_app_menu_action(&self, callback: Box) { + self.0.lock().menu_command = Some(callback); + } + + fn on_will_open_app_menu(&self, callback: Box) { + self.0.lock().will_open_menu = Some(callback); + } + + fn on_validate_app_menu_command(&self, callback: Box bool>) { + self.0.lock().validate_menu_command = Some(callback); + } + fn os_name(&self) -> &'static str { "macOS" } @@ -675,6 +691,15 @@ impl Platform for MacPlatform { } } + fn set_menus(&self, menus: Vec, dispatch_tree: Option<&DispatchTree>) { + unsafe { + let app: id = msg_send![APP_CLASS, sharedApplication]; + let mut state = self.0.lock(); + let actions = &mut state.menu_actions; + app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), actions, dispatch_tree)); + } + } + fn local_timezone(&self) -> UtcOffset { unsafe { let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone]; @@ -683,32 +708,6 @@ impl Platform for MacPlatform { } } - fn on_app_menu_action(&self, callback: Box) { - self.0.lock().menu_command = Some(callback); - } - - fn on_will_open_app_menu(&self, callback: Box) { - self.0.lock().will_open_menu = Some(callback); - } - - fn on_validate_app_menu_command(&self, callback: Box bool>) { - self.0.lock().validate_menu_command = Some(callback); - } - - // fn set_menus(&self, menus: Vec, keystroke_matcher: &KeymapMatcher) { - // unsafe { - // let app: id = msg_send![APP_CLASS, sharedApplication]; - // let mut state = self.0.lock(); - // let actions = &mut state.menu_actions; - // app.setMainMenu_(self.create_menu_bar( - // menus, - // app.delegate(), - // actions, - // keystroke_matcher, - // )); - // } - // } - fn path_for_auxiliary_executable(&self, name: &str) -> Result { unsafe { let bundle: id = NSBundle::mainBundle(); diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 952a9a96cac98e85863931f3e726d71b85e58896..b2a9279df4fbbaee20a507d3c795d596fa142a2a 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -1,6 +1,7 @@ use crate::{ - AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, - Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions, + AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DispatchTree, DisplayId, + ForegroundExecutor, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, + WindowOptions, }; use anyhow::{anyhow, Result}; use collections::VecDeque; @@ -205,6 +206,10 @@ impl Platform for TestPlatform { unimplemented!() } + fn set_menus(&self, _menus: Vec, _dispatch_tree: Option<&DispatchTree>) { + unimplemented!() + } + fn on_app_menu_action(&self, _callback: Box) { unimplemented!() } diff --git a/crates/settings2/src/settings_file.rs b/crates/settings2/src/settings_file.rs index c28e281895771a398e8a214961687df5002b1ccd..590079c51b52fe77a2c83ec4a862b27a0202ad1a 100644 --- a/crates/settings2/src/settings_file.rs +++ b/crates/settings2/src/settings_file.rs @@ -132,39 +132,3 @@ pub fn load_default_keymap(cx: &mut AppContext) { // KeymapFile::load_asset(asset_path, cx).unwrap(); // } } - -pub fn handle_keymap_file_changes( - mut user_keymap_file_rx: mpsc::UnboundedReceiver, - cx: &mut AppContext, -) { - cx.spawn(move |cx| async move { - // let mut settings_subscription = None; - while let Some(user_keymap_content) = user_keymap_file_rx.next().await { - if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() { - cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok(); - - // todo!() - // let mut old_base_keymap = cx.read(|cx| *settings::get::(cx)); - // drop(settings_subscription); - // settings_subscription = Some(cx.update(|cx| { - // cx.observe_global::(move |cx| { - // let new_base_keymap = *settings::get::(cx); - // if new_base_keymap != old_base_keymap { - // old_base_keymap = new_base_keymap.clone(); - // reload_keymaps(cx, &keymap_content); - // } - // }) - // })); - } - } - }) - .detach(); -} - -fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) { - // todo!() - // cx.clear_bindings(); - load_default_keymap(cx); - keymap_content.clone().add_to_cx(cx).log_err(); - // cx.set_menus(menus::menus()); -} diff --git a/crates/zed2/src/app_menus.rs b/crates/zed2/src/app_menus.rs new file mode 100644 index 0000000000000000000000000000000000000000..70b04e8f9be774d3e7d28e610f65bbabfb80c9b0 --- /dev/null +++ b/crates/zed2/src/app_menus.rs @@ -0,0 +1,175 @@ +use gpui::{Menu, MenuItem, OsAction}; + +#[cfg(target_os = "macos")] +pub fn app_menus() -> Vec> { + vec![ + Menu { + name: "Zed", + items: vec![ + MenuItem::action("About Zed…", super::About), + MenuItem::action("Check for Updates", auto_update::Check), + MenuItem::separator(), + MenuItem::submenu(Menu { + name: "Preferences", + items: vec![ + MenuItem::action("Open Settings", super::OpenSettings), + MenuItem::action("Open Key Bindings", super::OpenKeymap), + MenuItem::action("Open Default Settings", super::OpenDefaultSettings), + MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap), + MenuItem::action("Open Local Settings", super::OpenLocalSettings), + MenuItem::action("Select Theme", theme_selector::Toggle), + ], + }), + MenuItem::action("Install CLI", install_cli::Install), + MenuItem::separator(), + MenuItem::action("Hide Zed", super::Hide), + MenuItem::action("Hide Others", super::HideOthers), + MenuItem::action("Show All", super::ShowAll), + MenuItem::action("Quit", super::Quit), + ], + }, + Menu { + name: "File", + items: vec![ + MenuItem::action("New", workspace::NewFile), + MenuItem::action("New Window", workspace::NewWindow), + MenuItem::separator(), + MenuItem::action("Open…", workspace::Open), + // MenuItem::action("Open Recent...", recent_projects::OpenRecent), + MenuItem::separator(), + MenuItem::action("Add Folder to Project…", workspace::AddFolderToProject), + MenuItem::action("Save", workspace::Save { save_intent: None }), + MenuItem::action("Save As…", workspace::SaveAs), + MenuItem::action("Save All", workspace::SaveAll { save_intent: None }), + MenuItem::action( + "Close Editor", + workspace::CloseActiveItem { save_intent: None }, + ), + MenuItem::action("Close Window", workspace::CloseWindow), + ], + }, + Menu { + name: "Edit", + items: vec![ + MenuItem::os_action("Undo", editor::Undo, OsAction::Undo), + MenuItem::os_action("Redo", editor::Redo, OsAction::Redo), + MenuItem::separator(), + MenuItem::os_action("Cut", editor::Cut, OsAction::Cut), + MenuItem::os_action("Copy", editor::Copy, OsAction::Copy), + MenuItem::os_action("Paste", editor::Paste, OsAction::Paste), + MenuItem::separator(), + MenuItem::action("Find", search::buffer_search::Deploy { focus: true }), + MenuItem::action("Find In Project", workspace::NewSearch), + MenuItem::separator(), + MenuItem::action("Toggle Line Comment", editor::ToggleComments::default()), + MenuItem::action("Emoji & Symbols", editor::ShowCharacterPalette), + ], + }, + Menu { + name: "Selection", + items: vec![ + MenuItem::os_action("Select All", editor::SelectAll, OsAction::SelectAll), + MenuItem::action("Expand Selection", editor::SelectLargerSyntaxNode), + MenuItem::action("Shrink Selection", editor::SelectSmallerSyntaxNode), + MenuItem::separator(), + MenuItem::action("Add Cursor Above", editor::AddSelectionAbove), + MenuItem::action("Add Cursor Below", editor::AddSelectionBelow), + MenuItem::action( + "Select Next Occurrence", + editor::SelectNext { + replace_newest: false, + }, + ), + MenuItem::separator(), + MenuItem::action("Move Line Up", editor::MoveLineUp), + MenuItem::action("Move Line Down", editor::MoveLineDown), + MenuItem::action("Duplicate Selection", editor::DuplicateLine), + ], + }, + Menu { + name: "View", + items: vec![ + MenuItem::action("Zoom In", super::IncreaseBufferFontSize), + MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), + MenuItem::action("Reset Zoom", super::ResetBufferFontSize), + MenuItem::separator(), + MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), + MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock), + MenuItem::action("Toggle Bottom Dock", workspace::ToggleBottomDock), + MenuItem::action("Close All Docks", workspace::CloseAllDocks), + MenuItem::submenu(Menu { + name: "Editor Layout", + items: vec![ + MenuItem::action("Split Up", workspace::SplitUp), + MenuItem::action("Split Down", workspace::SplitDown), + MenuItem::action("Split Left", workspace::SplitLeft), + MenuItem::action("Split Right", workspace::SplitRight), + ], + }), + MenuItem::separator(), + MenuItem::action("Project Panel", project_panel::ToggleFocus), + MenuItem::action("Command Palette", command_palette::Toggle), + MenuItem::action("Diagnostics", diagnostics::Deploy), + MenuItem::separator(), + ], + }, + Menu { + name: "Go", + items: vec![ + MenuItem::action("Back", workspace::GoBack), + MenuItem::action("Forward", workspace::GoForward), + MenuItem::separator(), + MenuItem::action("Go to File", file_finder::Toggle), + // MenuItem::action("Go to Symbol in Project", project_symbols::Toggle), + MenuItem::action("Go to Symbol in Editor", outline::Toggle), + MenuItem::action("Go to Definition", editor::GoToDefinition), + MenuItem::action("Go to Type Definition", editor::GoToTypeDefinition), + MenuItem::action("Find All References", editor::FindAllReferences), + MenuItem::action("Go to Line/Column", go_to_line::Toggle), + MenuItem::separator(), + MenuItem::action("Next Problem", editor::GoToDiagnostic), + MenuItem::action("Previous Problem", editor::GoToPrevDiagnostic), + ], + }, + Menu { + name: "Window", + items: vec![ + MenuItem::action("Minimize", super::Minimize), + MenuItem::action("Zoom", super::Zoom), + MenuItem::separator(), + ], + }, + Menu { + name: "Help", + items: vec![ + MenuItem::action("Command Palette", command_palette::Toggle), + MenuItem::separator(), + MenuItem::action("View Telemetry", crate::OpenTelemetryLog), + MenuItem::action("View Dependency Licenses", crate::OpenLicenses), + MenuItem::action("Show Welcome", workspace::Welcome), + MenuItem::separator(), + // todo!(): Needs `feedback2` crate. + // MenuItem::action("Give us feedback", feedback::feedback_editor::GiveFeedback), + // MenuItem::action( + // "Copy System Specs Into Clipboard", + // feedback::CopySystemSpecsIntoClipboard, + // ), + // MenuItem::action("File Bug Report", feedback::FileBugReport), + // MenuItem::action("Request Feature", feedback::RequestFeature), + MenuItem::separator(), + MenuItem::action( + "Documentation", + crate::OpenBrowser { + url: "https://zed.dev/docs".into(), + }, + ), + MenuItem::action( + "Zed Twitter", + crate::OpenBrowser { + url: "https://twitter.com/zeddotdev".into(), + }, + ), + ], + }, + ] +} diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 6ca5d1a805b53ab1dabd43591b7353c0231aa365..7faafb2440c7e311641219461c2d79934702ddb9 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -22,8 +22,7 @@ use node_runtime::RealNodeRuntime; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use settings::{ - default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file, - Settings, SettingsStore, + default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, }; use simplelog::ConfigBuilder; use smol::process::Command; @@ -51,8 +50,9 @@ use uuid::Uuid; use welcome::{show_welcome_experience, FIRST_OPEN}; use workspace::{AppState, WorkspaceStore}; use zed2::{ - build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace, - languages, Assets, IsOnlyInstance, OpenListener, OpenRequest, + app_menus, build_window_options, ensure_only_instance, handle_cli_connection, + handle_keymap_file_changes, initialize_workspace, languages, Assets, IsOnlyInstance, + OpenListener, OpenRequest, }; mod open_listener; @@ -224,7 +224,7 @@ fn main() { // feedback::init(cx); welcome::init(cx); - // cx.set_menus(menus::menus()); + cx.set_menus(app_menus()); initialize_workspace(app_state.clone(), cx); if stdout_is_a_pty() { diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index abe8e7a86f19802f2ab8e7347ba87ba4b6a08050..7e69a2aee3ec799a7e5c6876da39e45386c6487f 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -1,11 +1,13 @@ #![allow(unused_variables, unused_mut)] //todo!() +mod app_menus; mod assets; pub mod languages; mod only_instance; mod open_listener; +pub use app_menus::*; pub use assets::*; use breadcrumbs::Breadcrumbs; use collections::VecDeque; @@ -18,8 +20,9 @@ pub use only_instance::*; pub use open_listener::*; use anyhow::{anyhow, Context as _}; +use futures::{channel::mpsc, StreamExt}; use project_panel::ProjectPanel; -use settings::{initial_local_settings_content, Settings}; +use settings::{initial_local_settings_content, load_default_keymap, KeymapFile, Settings}; use std::{borrow::Cow, ops::Deref, sync::Arc}; use terminal_view::terminal_panel::TerminalPanel; use util::{ @@ -561,6 +564,42 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { .detach(); } +pub fn handle_keymap_file_changes( + mut user_keymap_file_rx: mpsc::UnboundedReceiver, + cx: &mut AppContext, +) { + cx.spawn(move |cx| async move { + // let mut settings_subscription = None; + while let Some(user_keymap_content) = user_keymap_file_rx.next().await { + if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() { + cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok(); + + // todo!() + // let mut old_base_keymap = cx.read(|cx| *settings::get::(cx)); + // drop(settings_subscription); + // settings_subscription = Some(cx.update(|cx| { + // cx.observe_global::(move |cx| { + // let new_base_keymap = *settings::get::(cx); + // if new_base_keymap != old_base_keymap { + // old_base_keymap = new_base_keymap.clone(); + // reload_keymaps(cx, &keymap_content); + // } + // }) + // })); + } + } + }) + .detach(); +} + +fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) { + // todo!() + // cx.clear_bindings(); + load_default_keymap(cx); + keymap_content.clone().add_to_cx(cx).log_err(); + cx.set_menus(app_menus()); +} + fn open_local_settings_file( workspace: &mut Workspace, _: &OpenLocalSettings, From d8757845a988bc8d5f1de338962ad8fcf1d7129e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 5 Dec 2023 20:21:35 -0500 Subject: [PATCH 06/23] Wire up `NewFile` action --- crates/editor2/src/editor.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index a77e1dcc3b9621080e4e78ef94f80fff2c18112e..4b14ec42f1ed11b73220b4e785c4ceb1ad7cae45 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -92,6 +92,7 @@ use std::{ ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive}, path::Path, sync::Arc, + sync::Weak, time::{Duration, Instant}, }; pub use sum_tree::Bias; @@ -420,6 +421,16 @@ pub fn init(cx: &mut AppContext) { }, ) .detach(); + + cx.on_action(move |_: &workspace::NewFile, cx| { + let app_state = cx.global::>(); + if let Some(app_state) = app_state.upgrade() { + workspace::open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } + }); } trait InvalidationRegion { From ed31d36ac1013c06d07e142092b9618cbc7fbf4a Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 5 Dec 2023 20:24:01 -0500 Subject: [PATCH 07/23] Wire up `NewWindow` action --- crates/editor2/src/editor.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 4b14ec42f1ed11b73220b4e785c4ceb1ad7cae45..3fdccb2de42f578c1a9857a9b25403119f617e00 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -431,6 +431,15 @@ pub fn init(cx: &mut AppContext) { .detach(); } }); + cx.on_action(move |_: &workspace::NewWindow, cx| { + let app_state = cx.global::>(); + if let Some(app_state) = app_state.upgrade() { + workspace::open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } + }) } trait InvalidationRegion { From 5660c8f655359286c4f3138c925dec847b877e5a Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 5 Dec 2023 20:24:43 -0500 Subject: [PATCH 08/23] Add missing semicolon --- crates/editor2/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 3fdccb2de42f578c1a9857a9b25403119f617e00..07f00198de2dcb4a5281b868e1c1727c83fe3c43 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -439,7 +439,7 @@ pub fn init(cx: &mut AppContext) { }) .detach(); } - }) + }); } trait InvalidationRegion { From 3f9fe58c48a9722a3dd8ccf62081db2dbbc404a1 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:00:57 +0100 Subject: [PATCH 09/23] Signed out state is looking good --- Cargo.lock | 1 + crates/copilot2/Cargo.toml | 1 + crates/copilot2/src/sign_in.rs | 278 +++++++++++++++++------------- crates/ui2/src/components/icon.rs | 3 + 4 files changed, 164 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e689388a132306158b634fd1776476233d7a03f3..6b123f9061d4ba7b15d606e7896c65c22a3c8245 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2121,6 +2121,7 @@ dependencies = [ "settings2", "smol", "theme2", + "ui2", "util", ] diff --git a/crates/copilot2/Cargo.toml b/crates/copilot2/Cargo.toml index 9a9243b32eecb766451a3f7f89940227fe6059fa..b04a7d1246ebfbcbb03dbdf179d2633934cecd98 100644 --- a/crates/copilot2/Cargo.toml +++ b/crates/copilot2/Cargo.toml @@ -28,6 +28,7 @@ theme = { package = "theme2", path = "../theme2" } lsp = { package = "lsp2", path = "../lsp2" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } +ui = { package = "ui2", path = "../ui2" } async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } async-tar = "0.4.2" anyhow.workspace = true diff --git a/crates/copilot2/src/sign_in.rs b/crates/copilot2/src/sign_in.rs index 7973d935af1be1c455bb2b90a0ed8dcf2c1b3566..e39deeff83b168d653546c237eba8464f1516c1e 100644 --- a/crates/copilot2/src/sign_in.rs +++ b/crates/copilot2/src/sign_in.rs @@ -11,11 +11,15 @@ const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; -use crate::{Copilot, Status}; +use crate::{request::PromptUserDeviceFlow, Copilot, Status}; use gpui::{ - px, size, AppContext, Bounds, Div, GlobalPixels, Point, Render, ViewContext, VisualContext, - WindowBounds, WindowHandle, WindowKind, WindowOptions, + div, px, red, size, AnyElement, AppContext, Bounds, ClipboardItem, Div, Element, GlobalPixels, + InteractiveElement, IntoElement, MouseButton, ParentElement, Point, Render, Stateful, + StatefulInteractiveElement, Styled, ViewContext, VisualContext, WindowBounds, WindowHandle, + WindowKind, WindowOptions, }; +use theme::ActiveTheme; +use ui::{h_stack, v_stack, Button, Clickable, Icon, IconElement, Label}; pub fn init(cx: &mut AppContext) { if let Some(copilot) = Copilot::global(cx) { @@ -72,13 +76,14 @@ fn create_copilot_auth_window( center: true, focus: true, show: true, - kind: WindowKind::Normal, + kind: WindowKind::PopUp, is_movable: true, display_id: None, }; - cx.open_window(window_options, |cx| { + let window = cx.open_window(window_options, |cx| { cx.build_view(|_| CopilotCodeVerification::new(status.clone())) - }) + }); + window } pub struct CopilotCodeVerification { @@ -99,119 +104,138 @@ impl CopilotCodeVerification { cx.notify(); } - // fn render_device_code( - // data: &PromptUserDeviceFlow, - // style: &theme::Copilot, - // cx: &mut ViewContext, - // ) -> impl IntoAnyElement { - // let copied = cx - // .read_from_clipboard() - // .map(|item| item.text() == &data.user_code) - // .unwrap_or(false); - - // let device_code_style = &style.auth.prompting.device_code; - - // MouseEventHandler::new::(0, cx, |state, _cx| { - // Flex::row() - // .with_child( - // Label::new(data.user_code.clone(), device_code_style.text.clone()) - // .aligned() - // .contained() - // .with_style(device_code_style.left_container) - // .constrained() - // .with_width(device_code_style.left), - // ) - // .with_child( - // Label::new( - // if copied { "Copied!" } else { "Copy" }, - // device_code_style.cta.style_for(state).text.clone(), - // ) - // .aligned() - // .contained() - // .with_style(*device_code_style.right_container.style_for(state)) - // .constrained() - // .with_width(device_code_style.right), - // ) - // .contained() - // .with_style(device_code_style.cta.style_for(state).container) - // }) - // .on_click(gpui::platform::MouseButton::Left, { - // let user_code = data.user_code.clone(); - // move |_, _, cx| { - // cx.platform() - // .write_to_clipboard(ClipboardItem::new(user_code.clone())); - // cx.notify(); - // } - // }) - // .with_cursor_style(gpui::platform::CursorStyle::PointingHand) - // } + fn render_device_code( + data: &PromptUserDeviceFlow, + cx: &mut ViewContext, + ) -> impl IntoElement { + let copied = cx + .read_from_clipboard() + .map(|item| item.text() == &data.user_code) + .unwrap_or(false); + h_stack() + .cursor_pointer() + .justify_between() + .on_mouse_down(gpui::MouseButton::Left, { + let user_code = data.user_code.clone(); + move |_, cx| { + dbg!("Copied"); + cx.write_to_clipboard(ClipboardItem::new(user_code.clone())); + cx.notify(); + } + }) + .child(Label::new(data.user_code.clone())) + .child(Label::new(if copied { "Copied!" } else { "Copy" })) - // fn render_prompting_modal( - // connect_clicked: bool, - // data: &PromptUserDeviceFlow, - // style: &theme::Copilot, - // cx: &mut ViewContext, - // ) -> AnyElement { - // enum ConnectButton {} + // MouseEventHandler::new::(0, cx, |state, _cx| { + // Flex::row() + // .with_child( + // Label::new(data.user_code.clone(), device_code_style.text.clone()) + // .aligned() + // .contained() + // .with_style(device_code_style.left_container) + // .constrained() + // .with_width(device_code_style.left), + // ) + // .with_child( + // Label::new( + // if copied { "Copied!" } else { "Copy" }, + // device_code_style.cta.style_for(state).text.clone(), + // ) + // .aligned() + // .contained() + // .with_style(*device_code_style.right_container.style_for(state)) + // .constrained() + // .with_width(device_code_style.right), + // ) + // .contained() + // .with_style(device_code_style.cta.style_for(state).container) + // }) + // .on_click(gpui::platform::MouseButton::Left, { + // + // move |_, _, cx| { + // + // } + // }) + // .with_cursor_style(gpui::platform::CursorStyle::PointingHand) + } - // Flex::column() - // .with_child( - // Flex::column() - // .with_children([ - // Label::new( - // "Enable Copilot by connecting", - // style.auth.prompting.subheading.text.clone(), - // ) - // .aligned(), - // Label::new( - // "your existing license.", - // style.auth.prompting.subheading.text.clone(), - // ) - // .aligned(), - // ]) - // .align_children_center() - // .contained() - // .with_style(style.auth.prompting.subheading.container), - // ) - // .with_child(Self::render_device_code(data, &style, cx)) - // .with_child( - // Flex::column() - // .with_children([ - // Label::new( - // "Paste this code into GitHub after", - // style.auth.prompting.hint.text.clone(), - // ) - // .aligned(), - // Label::new( - // "clicking the button below.", - // style.auth.prompting.hint.text.clone(), - // ) - // .aligned(), - // ]) - // .align_children_center() - // .contained() - // .with_style(style.auth.prompting.hint.container.clone()), - // ) - // .with_child(theme::ui::cta_button::( - // if connect_clicked { - // "Waiting for connection..." - // } else { - // "Connect to GitHub" - // }, - // style.auth.content_width, - // &style.auth.cta_button, - // cx, - // { - // let verification_uri = data.verification_uri.clone(); - // move |_, verification, cx| { - // cx.platform().open_url(&verification_uri); - // verification.connect_clicked = true; - // } - // }, - // )) - // .align_children_center() - // .into_any() - // } + fn render_prompting_modal( + connect_clicked: bool, + data: &PromptUserDeviceFlow, + cx: &mut ViewContext, + ) -> impl Element { + let connect_button_label = if connect_clicked { + "Waiting for connection..." + } else { + "Connect to Github" + }; + v_stack() + .child( + v_stack() + .flex_1() + .w_full() + .items_center() + .justify_between() + .children([ + h_stack() + .items_center() + .child(Label::new("Enable Copilot by connecting")), + h_stack() + .items_center() + .child(Label::new("your existing license")), + ]), + ) + .child(Self::render_device_code(data, cx)) + .child(Label::new("Paste this code into GitHub after").size(ui::LabelSize::Small)) + .child(Label::new("clicking the button below.").size(ui::LabelSize::Small)) + .child( + Button::new("connect-button", connect_button_label).on_click({ + let verification_uri = data.verification_uri.clone(); + cx.listener(move |this, _, cx| { + cx.open_url(&verification_uri); + this.connect_clicked = true; + }) + }), + ) + // Flex::column() + // .with_child(Self::render_device_code(data, &style, cx)) + // .with_child( + // Flex::column() + // .with_children([ + // Label::new( + // "Paste this code into GitHub after", + // style.auth.prompting.hint.text.clone(), + // ) + // .aligned(), + // Label::new( + // "clicking the button below.", + // style.auth.prompting.hint.text.clone(), + // ) + // .aligned(), + // ]) + // .align_children_center() + // .contained() + // .with_style(style.auth.prompting.hint.container.clone()), + // ) + // .with_child(theme::ui::cta_button::( + // if connect_clicked { + // "Waiting for connection..." + // } else { + // "Connect to GitHub" + // }, + // style.auth.content_width, + // &style.auth.cta_button, + // cx, + // { + // let verification_uri = data.verification_uri.clone(); + // move |_, verification, cx| { + // cx.platform().open_url(&verification_uri); + // verification.connect_clicked = true; + // } + // }, + // )) + // .align_children_center() + } // fn render_enabled_modal( // style: &theme::Copilot, @@ -316,10 +340,26 @@ impl CopilotCodeVerification { } impl Render for CopilotCodeVerification { - type Element = Div; + type Element = Stateful
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - todo!() + let prompt = match &self.status { + Status::SigningIn { prompt } => prompt.as_ref(), + _ => None, + }; + div() + .id("copilot code verification") + .flex() + .flex_col() + .size_full() + .items_center() + .p_10() + .bg(cx.theme().colors().element_background) + .child(ui::Label::new("Connect Copilot to Zed")) + .child(IconElement::new(Icon::ZedXCopilot)) + .children( + prompt.map(|data| Self::render_prompting_modal(self.connect_clicked, data, cx)), + ) } } diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index a993a54e15463d14cbdf8c14325aec96480204e6..e9a13bd009b0afd6b23e89e773f81d93684d8989 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -81,6 +81,7 @@ pub enum Icon { Shift, Option, Return, + ZedXCopilot, } impl Icon { @@ -109,6 +110,7 @@ impl Icon { Icon::Close => "icons/x.svg", Icon::Collab => "icons/user_group_16.svg", Icon::Copilot => "icons/copilot.svg", + Icon::CopilotInit => "icons/copilot_init.svg", Icon::CopilotError => "icons/copilot_error.svg", Icon::CopilotDisabled => "icons/copilot_disabled.svg", @@ -155,6 +157,7 @@ impl Icon { Icon::Shift => "icons/shift.svg", Icon::Option => "icons/option.svg", Icon::Return => "icons/return.svg", + Icon::ZedXCopilot => "icons/zed_x_copilot.svg", } } } From 7998e8281c384eb58a6099df0111699856b84079 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:14:18 +0100 Subject: [PATCH 10/23] Barebones Copilot prompt Filter out sign in/sign out when user is signed in/not signed in --- crates/copilot2/src/copilot2.rs | 14 ++++-- crates/copilot2/src/sign_in.rs | 87 ++++++++++++++++++++++----------- 2 files changed, 69 insertions(+), 32 deletions(-) diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index d23d25119b152be1c232304d2992af81af3ab9e4..a829f68f41b266dc9a5896a2fffe2a926ac3c805 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/crates/copilot2/src/copilot2.rs @@ -60,8 +60,8 @@ pub fn init( TypeId::of::(), TypeId::of::(), ]; - let copilot_auth_action_types = [TypeId::of::(), TypeId::of::()]; - + let copilot_auth_action_types = [TypeId::of::()]; + let copilot_no_auth_action_types = [TypeId::of::()]; let status = handle.read(cx).status(); let filter = cx.default_global::(); @@ -69,8 +69,14 @@ pub fn init( Status::Disabled => { filter.hidden_action_types.extend(copilot_action_types); filter.hidden_action_types.extend(copilot_auth_action_types); + filter + .hidden_action_types + .extend(copilot_no_auth_action_types); } Status::Authorized => { + filter + .hidden_action_types + .extend(copilot_no_auth_action_types); for type_id in copilot_action_types .iter() .chain(&copilot_auth_action_types) @@ -80,7 +86,8 @@ pub fn init( } _ => { filter.hidden_action_types.extend(copilot_action_types); - for type_id in &copilot_auth_action_types { + filter.hidden_action_types.extend(copilot_auth_action_types); + for type_id in &copilot_no_auth_action_types { filter.hidden_action_types.remove(type_id); } } @@ -97,6 +104,7 @@ pub fn init( } }); cx.on_action(|_: &SignOut, cx| { + dbg!("Signing out"); if let Some(copilot) = Copilot::global(cx) { copilot .update(cx, |copilot, cx| copilot.sign_out(cx)) diff --git a/crates/copilot2/src/sign_in.rs b/crates/copilot2/src/sign_in.rs index e39deeff83b168d653546c237eba8464f1516c1e..dd107532fa1f55cf2baa9d5d1305c53f37814022 100644 --- a/crates/copilot2/src/sign_in.rs +++ b/crates/copilot2/src/sign_in.rs @@ -13,13 +13,12 @@ const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; use crate::{request::PromptUserDeviceFlow, Copilot, Status}; use gpui::{ - div, px, red, size, AnyElement, AppContext, Bounds, ClipboardItem, Div, Element, GlobalPixels, - InteractiveElement, IntoElement, MouseButton, ParentElement, Point, Render, Stateful, - StatefulInteractiveElement, Styled, ViewContext, VisualContext, WindowBounds, WindowHandle, - WindowKind, WindowOptions, + div, size, AppContext, Bounds, ClipboardItem, Div, Element, GlobalPixels, InteractiveElement, + IntoElement, ParentElement, Point, Render, Stateful, Styled, ViewContext, VisualContext, + WindowBounds, WindowHandle, WindowKind, WindowOptions, }; use theme::ActiveTheme; -use ui::{h_stack, v_stack, Button, Clickable, Icon, IconElement, Label}; +use ui::{h_stack, v_stack, Button, Clickable, Color, Icon, IconElement, Label}; pub fn init(cx: &mut AppContext) { if let Some(copilot) = Copilot::global(cx) { @@ -56,7 +55,9 @@ pub fn init(cx: &mut AppContext) { } _ => { if let Some(code_verification) = verification_window.take() { - code_verification.update(cx, |_, cx| cx.remove_window()); + code_verification + .update(cx, |_, cx| cx.remove_window()) + .ok(); } } } @@ -118,12 +119,12 @@ impl CopilotCodeVerification { .on_mouse_down(gpui::MouseButton::Left, { let user_code = data.user_code.clone(); move |_, cx| { - dbg!("Copied"); cx.write_to_clipboard(ClipboardItem::new(user_code.clone())); cx.notify(); } }) .child(Label::new(data.user_code.clone())) + .child(div()) .child(Label::new(if copied { "Copied!" } else { "Copy" })) // MouseEventHandler::new::(0, cx, |state, _cx| { @@ -170,24 +171,18 @@ impl CopilotCodeVerification { "Connect to Github" }; v_stack() + .flex_1() + .items_center() + .justify_between() + .w_full() + .child(Label::new( + "Enable Copilot by connecting your existing license", + )) + .child(Self::render_device_code(data, cx)) .child( - v_stack() - .flex_1() - .w_full() - .items_center() - .justify_between() - .children([ - h_stack() - .items_center() - .child(Label::new("Enable Copilot by connecting")), - h_stack() - .items_center() - .child(Label::new("your existing license")), - ]), + Label::new("Paste this code into GitHub after clicking the button below.") + .size(ui::LabelSize::Small), ) - .child(Self::render_device_code(data, cx)) - .child(Label::new("Paste this code into GitHub after").size(ui::LabelSize::Small)) - .child(Label::new("clicking the button below.").size(ui::LabelSize::Small)) .child( Button::new("connect-button", connect_button_label).on_click({ let verification_uri = data.verification_uri.clone(); @@ -236,6 +231,17 @@ impl CopilotCodeVerification { // )) // .align_children_center() } + fn render_enabled_modal() -> impl Element { + v_stack() + .child(Label::new("Copilot Enabled!")) + .child(Label::new( + "You can update your settings or sign out from the Copilot menu in the status bar.", + )) + .child( + Button::new("copilot-enabled-done-button", "Done") + .on_click(|_, cx| cx.remove_window()), + ) + } // fn render_enabled_modal( // style: &theme::Copilot, @@ -280,7 +286,22 @@ impl CopilotCodeVerification { // .align_children_center() // .into_any() // } - + fn render_unauthorized_modal() -> impl Element { + v_stack() + .child(Label::new( + "Enable Copilot by connecting your existing license.", + )) + .child( + Label::new("You must have an active Copilot license to use it in Zed.") + .color(Color::Warning), + ) + .child( + Button::new("copilot-subscribe-button", "Subscibe on Github").on_click(|_, cx| { + cx.remove_window(); + cx.open_url(COPILOT_SIGN_UP_URL) + }), + ) + } // fn render_unauthorized_modal( // style: &theme::Copilot, // cx: &mut ViewContext, @@ -344,8 +365,18 @@ impl Render for CopilotCodeVerification { fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let prompt = match &self.status { - Status::SigningIn { prompt } => prompt.as_ref(), - _ => None, + Status::SigningIn { + prompt: Some(prompt), + } => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(), + Status::Unauthorized => { + self.connect_clicked = false; + Self::render_unauthorized_modal().into_any_element() + } + Status::Authorized => { + self.connect_clicked = false; + Self::render_enabled_modal().into_any_element() + } + _ => div().into_any_element(), }; div() .id("copilot code verification") @@ -357,9 +388,7 @@ impl Render for CopilotCodeVerification { .bg(cx.theme().colors().element_background) .child(ui::Label::new("Connect Copilot to Zed")) .child(IconElement::new(Icon::ZedXCopilot)) - .children( - prompt.map(|data| Self::render_prompting_modal(self.connect_clicked, data, cx)), - ) + .child(prompt) } } From 1b0ec82caa66bcabd55b2fde2fd9ba789fbd27ce Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:16:19 +0100 Subject: [PATCH 11/23] Remove old UI code, remove dbg! --- crates/copilot2/src/copilot2.rs | 1 - crates/copilot2/src/sign_in.rs | 241 +------------------------------- 2 files changed, 2 insertions(+), 240 deletions(-) diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index a829f68f41b266dc9a5896a2fffe2a926ac3c805..9c5483d634c201019873d9aa99fdaccd4b361cec 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/crates/copilot2/src/copilot2.rs @@ -104,7 +104,6 @@ pub fn init( } }); cx.on_action(|_: &SignOut, cx| { - dbg!("Signing out"); if let Some(copilot) = Copilot::global(cx) { copilot .update(cx, |copilot, cx| copilot.sign_out(cx)) diff --git a/crates/copilot2/src/sign_in.rs b/crates/copilot2/src/sign_in.rs index dd107532fa1f55cf2baa9d5d1305c53f37814022..8da34c427f9eb98c4b2bee031761b4304b498f13 100644 --- a/crates/copilot2/src/sign_in.rs +++ b/crates/copilot2/src/sign_in.rs @@ -1,16 +1,3 @@ -// TODO add logging in -// use crate::{request::PromptUserDeviceFlow, Copilot, Status}; -// use gpui::{ -// elements::*, -// geometry::rect::RectF, -// platform::{WindowBounds, WindowKind, WindowOptions}, -// AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext, -// WindowHandle, -// }; -// use theme::ui::modal; - -const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; - use crate::{request::PromptUserDeviceFlow, Copilot, Status}; use gpui::{ div, size, AppContext, Bounds, ClipboardItem, Div, Element, GlobalPixels, InteractiveElement, @@ -20,6 +7,8 @@ use gpui::{ use theme::ActiveTheme; use ui::{h_stack, v_stack, Button, Clickable, Color, Icon, IconElement, Label}; +const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; + pub fn init(cx: &mut AppContext) { if let Some(copilot) = Copilot::global(cx) { let mut verification_window: Option> = None; @@ -126,38 +115,6 @@ impl CopilotCodeVerification { .child(Label::new(data.user_code.clone())) .child(div()) .child(Label::new(if copied { "Copied!" } else { "Copy" })) - - // MouseEventHandler::new::(0, cx, |state, _cx| { - // Flex::row() - // .with_child( - // Label::new(data.user_code.clone(), device_code_style.text.clone()) - // .aligned() - // .contained() - // .with_style(device_code_style.left_container) - // .constrained() - // .with_width(device_code_style.left), - // ) - // .with_child( - // Label::new( - // if copied { "Copied!" } else { "Copy" }, - // device_code_style.cta.style_for(state).text.clone(), - // ) - // .aligned() - // .contained() - // .with_style(*device_code_style.right_container.style_for(state)) - // .constrained() - // .with_width(device_code_style.right), - // ) - // .contained() - // .with_style(device_code_style.cta.style_for(state).container) - // }) - // .on_click(gpui::platform::MouseButton::Left, { - // - // move |_, _, cx| { - // - // } - // }) - // .with_cursor_style(gpui::platform::CursorStyle::PointingHand) } fn render_prompting_modal( @@ -192,44 +149,6 @@ impl CopilotCodeVerification { }) }), ) - // Flex::column() - // .with_child(Self::render_device_code(data, &style, cx)) - // .with_child( - // Flex::column() - // .with_children([ - // Label::new( - // "Paste this code into GitHub after", - // style.auth.prompting.hint.text.clone(), - // ) - // .aligned(), - // Label::new( - // "clicking the button below.", - // style.auth.prompting.hint.text.clone(), - // ) - // .aligned(), - // ]) - // .align_children_center() - // .contained() - // .with_style(style.auth.prompting.hint.container.clone()), - // ) - // .with_child(theme::ui::cta_button::( - // if connect_clicked { - // "Waiting for connection..." - // } else { - // "Connect to GitHub" - // }, - // style.auth.content_width, - // &style.auth.cta_button, - // cx, - // { - // let verification_uri = data.verification_uri.clone(); - // move |_, verification, cx| { - // cx.platform().open_url(&verification_uri); - // verification.connect_clicked = true; - // } - // }, - // )) - // .align_children_center() } fn render_enabled_modal() -> impl Element { v_stack() @@ -243,49 +162,6 @@ impl CopilotCodeVerification { ) } - // fn render_enabled_modal( - // style: &theme::Copilot, - // cx: &mut ViewContext, - // ) -> AnyElement { - // enum DoneButton {} - - // let enabled_style = &style.auth.authorized; - // Flex::column() - // .with_child( - // Label::new("Copilot Enabled!", enabled_style.subheading.text.clone()) - // .contained() - // .with_style(enabled_style.subheading.container) - // .aligned(), - // ) - // .with_child( - // Flex::column() - // .with_children([ - // Label::new( - // "You can update your settings or", - // enabled_style.hint.text.clone(), - // ) - // .aligned(), - // Label::new( - // "sign out from the Copilot menu in", - // enabled_style.hint.text.clone(), - // ) - // .aligned(), - // Label::new("the status bar.", enabled_style.hint.text.clone()).aligned(), - // ]) - // .align_children_center() - // .contained() - // .with_style(enabled_style.hint.container), - // ) - // .with_child(theme::ui::cta_button::( - // "Done", - // style.auth.content_width, - // &style.auth.cta_button, - // cx, - // |_, _, cx| cx.remove_window(), - // )) - // .align_children_center() - // .into_any() - // } fn render_unauthorized_modal() -> impl Element { v_stack() .child(Label::new( @@ -302,62 +178,6 @@ impl CopilotCodeVerification { }), ) } - // fn render_unauthorized_modal( - // style: &theme::Copilot, - // cx: &mut ViewContext, - // ) -> AnyElement { - // let unauthorized_style = &style.auth.not_authorized; - - // Flex::column() - // .with_child( - // Flex::column() - // .with_children([ - // Label::new( - // "Enable Copilot by connecting", - // unauthorized_style.subheading.text.clone(), - // ) - // .aligned(), - // Label::new( - // "your existing license.", - // unauthorized_style.subheading.text.clone(), - // ) - // .aligned(), - // ]) - // .align_children_center() - // .contained() - // .with_style(unauthorized_style.subheading.container), - // ) - // .with_child( - // Flex::column() - // .with_children([ - // Label::new( - // "You must have an active copilot", - // unauthorized_style.warning.text.clone(), - // ) - // .aligned(), - // Label::new( - // "license to use it in Zed.", - // unauthorized_style.warning.text.clone(), - // ) - // .aligned(), - // ]) - // .align_children_center() - // .contained() - // .with_style(unauthorized_style.warning.container), - // ) - // .with_child(theme::ui::cta_button::( - // "Subscribe on GitHub", - // style.auth.content_width, - // &style.auth.cta_button, - // cx, - // |_, _, cx| { - // cx.remove_window(); - // cx.platform().open_url(COPILOT_SIGN_UP_URL) - // }, - // )) - // .align_children_center() - // .into_any() - // } } impl Render for CopilotCodeVerification { @@ -391,60 +211,3 @@ impl Render for CopilotCodeVerification { .child(prompt) } } - -// impl Entity for CopilotCodeVerification { -// type Event = (); -// } - -// impl View for CopilotCodeVerification { -// fn ui_name() -> &'static str { -// "CopilotCodeVerification" -// } - -// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { -// cx.notify() -// } - -// fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { -// cx.notify() -// } - -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// enum ConnectModal {} - -// let style = theme::current(cx).clone(); - -// modal::( -// "Connect Copilot to Zed", -// &style.copilot.modal, -// cx, -// |cx| { -// Flex::column() -// .with_children([ -// theme::ui::icon(&style.copilot.auth.header).into_any(), -// match &self.status { -// Status::SigningIn { -// prompt: Some(prompt), -// } => Self::render_prompting_modal( -// self.connect_clicked, -// &prompt, -// &style.copilot, -// cx, -// ), -// Status::Unauthorized => { -// self.connect_clicked = false; -// Self::render_unauthorized_modal(&style.copilot, cx) -// } -// Status::Authorized => { -// self.connect_clicked = false; -// Self::render_enabled_modal(&style.copilot, cx) -// } -// _ => Empty::new().into_any(), -// }, -// ]) -// .align_children_center() -// }, -// ) -// .into_any() -// } -// } From 0ef97edd6edbab2cdf8b0028b6a82c06c26e81ea Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Dec 2023 16:57:16 +0200 Subject: [PATCH 12/23] Format the CI file with Zed's default prettier --- .github/workflows/ci.yml | 272 +++++++++++++------------- .github/workflows/release_nightly.yml | 160 +++++++-------- 2 files changed, 216 insertions(+), 216 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bfc0ab683e859f2675846499a518b8df220c7e0..c499b1933a26376458c8bd02376734fd7911a9ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,144 +1,144 @@ name: CI on: - push: - branches: - - main - - "v[0-9]+.[0-9]+.x" - tags: - - "v*" - pull_request: - branches: - - "**" + push: + branches: + - main + - "v[0-9]+.[0-9]+.x" + tags: + - "v*" + pull_request: + branches: + - "**" env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 jobs: - rustfmt: - name: Check formatting - runs-on: - - self-hosted - - test - steps: - - name: Checkout repo - uses: actions/checkout@v3 - with: - clean: false - submodules: "recursive" - - - name: Set up default .cargo/config.toml - run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml - - - name: Run rustfmt - uses: ./.github/actions/check_formatting - - tests: - name: Run tests - runs-on: - - self-hosted - - test - needs: rustfmt - steps: - - name: Checkout repo - uses: actions/checkout@v3 - with: - clean: false - submodules: "recursive" - - - name: Run tests - uses: ./.github/actions/run_tests - - - name: Build collab - run: cargo build -p collab - - - name: Build other binaries - run: cargo build --workspace --bins --all-features - - bundle: - name: Bundle app - runs-on: - - self-hosted - - bundle - if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} - needs: tests - env: - MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} - MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} - APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} - APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} - steps: - - name: Install Rust - run: | - rustup set profile minimal - rustup update stable - rustup target add aarch64-apple-darwin - rustup target add x86_64-apple-darwin - rustup target add wasm32-wasi - - - name: Install Node - uses: actions/setup-node@v3 - with: - node-version: "18" - - - name: Checkout repo - uses: actions/checkout@v3 - with: - clean: false - submodules: "recursive" - - - name: Limit target directory size - run: script/clear-target-dir-if-larger-than 100 - - - name: Determine version and release channel - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - run: | - set -eu - - version=$(script/get-crate-version zed) - channel=$(cat crates/zed/RELEASE_CHANNEL) - echo "Publishing version: ${version} on release channel ${channel}" - echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV - - expected_tag_name="" - case ${channel} in - stable) - expected_tag_name="v${version}";; - preview) - expected_tag_name="v${version}-pre";; - nightly) - expected_tag_name="v${version}-nightly";; - *) - echo "can't publish a release on channel ${channel}" - exit 1;; - esac - if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then - echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}" - exit 1 - fi - - - name: Generate license file - run: script/generate-licenses - - - name: Create app bundle - run: script/bundle - - - name: Upload app bundle to workflow run if main branch or specific label - uses: actions/upload-artifact@v3 - if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} - with: - name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg - path: target/release/Zed.dmg - - - uses: softprops/action-gh-release@v1 - name: Upload app bundle to release - if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }} - with: - draft: true - prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }} - files: target/release/Zed.dmg - body: "" + rustfmt: + name: Check formatting + runs-on: + - self-hosted + - test + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + clean: false + submodules: "recursive" + + - name: Set up default .cargo/config.toml + run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml + + - name: Run rustfmt + uses: ./.github/actions/check_formatting + + tests: + name: Run tests + runs-on: + - self-hosted + - test + needs: rustfmt + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + clean: false + submodules: "recursive" + + - name: Run tests + uses: ./.github/actions/run_tests + + - name: Build collab + run: cargo build -p collab + + - name: Build other binaries + run: cargo build --workspace --bins --all-features + + bundle: + name: Bundle app + runs-on: + - self-hosted + - bundle + if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} + needs: tests env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} + APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} + steps: + - name: Install Rust + run: | + rustup set profile minimal + rustup update stable + rustup target add aarch64-apple-darwin + rustup target add x86_64-apple-darwin + rustup target add wasm32-wasi + + - name: Install Node + uses: actions/setup-node@v3 + with: + node-version: "18" + + - name: Checkout repo + uses: actions/checkout@v3 + with: + clean: false + submodules: "recursive" + + - name: Limit target directory size + run: script/clear-target-dir-if-larger-than 100 + + - name: Determine version and release channel + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + run: | + set -eu + + version=$(script/get-crate-version zed) + channel=$(cat crates/zed/RELEASE_CHANNEL) + echo "Publishing version: ${version} on release channel ${channel}" + echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV + + expected_tag_name="" + case ${channel} in + stable) + expected_tag_name="v${version}";; + preview) + expected_tag_name="v${version}-pre";; + nightly) + expected_tag_name="v${version}-nightly";; + *) + echo "can't publish a release on channel ${channel}" + exit 1;; + esac + if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then + echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}" + exit 1 + fi + + - name: Generate license file + run: script/generate-licenses + + - name: Create app bundle + run: script/bundle + + - name: Upload app bundle to workflow run if main branch or specific label + uses: actions/upload-artifact@v3 + if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} + with: + name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg + path: target/release/Zed.dmg + + - uses: softprops/action-gh-release@v1 + name: Upload app bundle to release + if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }} + with: + draft: true + prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }} + files: target/release/Zed.dmg + body: "" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 7b08c52c61395b09e9925ddb99a31470049b021b..38552646c343d5f44975e33146a4cd38dc3ab4d3 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -1,98 +1,98 @@ name: Release Nightly on: - schedule: - # Fire every night at 1:00am - - cron: "0 1 * * *" - push: - tags: - - "nightly" + schedule: + # Fire every night at 1:00am + - cron: "0 1 * * *" + push: + tags: + - "nightly" env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 jobs: - rustfmt: - name: Check formatting - runs-on: - - self-hosted - - test - steps: - - name: Checkout repo - uses: actions/checkout@v3 - with: - clean: false - submodules: "recursive" + rustfmt: + name: Check formatting + runs-on: + - self-hosted + - test + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + clean: false + submodules: "recursive" - - name: Run rustfmt - uses: ./.github/actions/check_formatting + - name: Run rustfmt + uses: ./.github/actions/check_formatting - tests: - name: Run tests - runs-on: - - self-hosted - - test - needs: rustfmt - steps: - - name: Checkout repo - uses: actions/checkout@v3 - with: - clean: false - submodules: "recursive" + tests: + name: Run tests + runs-on: + - self-hosted + - test + needs: rustfmt + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + clean: false + submodules: "recursive" - - name: Run tests - uses: ./.github/actions/run_tests + - name: Run tests + uses: ./.github/actions/run_tests - bundle: - name: Bundle app - runs-on: - - self-hosted - - bundle - needs: tests - env: - MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} - MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} - APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} - APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} - DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} - DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }} - steps: - - name: Install Rust - run: | - rustup set profile minimal - rustup update stable - rustup target add aarch64-apple-darwin - rustup target add x86_64-apple-darwin - rustup target add wasm32-wasi + bundle: + name: Bundle app + runs-on: + - self-hosted + - bundle + needs: tests + env: + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} + APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} + DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} + DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }} + steps: + - name: Install Rust + run: | + rustup set profile minimal + rustup update stable + rustup target add aarch64-apple-darwin + rustup target add x86_64-apple-darwin + rustup target add wasm32-wasi - - name: Install Node - uses: actions/setup-node@v3 - with: - node-version: "18" + - name: Install Node + uses: actions/setup-node@v3 + with: + node-version: "18" - - name: Checkout repo - uses: actions/checkout@v3 - with: - clean: false - submodules: "recursive" + - name: Checkout repo + uses: actions/checkout@v3 + with: + clean: false + submodules: "recursive" - - name: Limit target directory size - run: script/clear-target-dir-if-larger-than 100 + - name: Limit target directory size + run: script/clear-target-dir-if-larger-than 100 - - name: Set release channel to nightly - run: | - set -eu - version=$(git rev-parse --short HEAD) - echo "Publishing version: ${version} on release channel nightly" - echo "nightly" > crates/zed/RELEASE_CHANNEL + - name: Set release channel to nightly + run: | + set -eu + version=$(git rev-parse --short HEAD) + echo "Publishing version: ${version} on release channel nightly" + echo "nightly" > crates/zed/RELEASE_CHANNEL - - name: Generate license file - run: script/generate-licenses + - name: Generate license file + run: script/generate-licenses - - name: Create app bundle - run: script/bundle -2 + - name: Create app bundle + run: script/bundle -2 - - name: Upload Zed Nightly - run: script/upload-nightly + - name: Upload Zed Nightly + run: script/upload-nightly From a58f3934581e943fedce70579f674c5e528750c2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Dec 2023 16:58:49 +0200 Subject: [PATCH 13/23] Do not bundle Zed on `main` branch commits --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c499b1933a26376458c8bd02376734fd7911a9ed..8ac2912424bb8bf10014ac1b8961405cd2ba47c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: runs-on: - self-hosted - bundle - if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} + if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} needs: tests env: MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} From d09dfe01f5ad14f864063efd660f551f4377fff5 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:15:53 +0100 Subject: [PATCH 14/23] Wire up global actions Added an ephemeral root node so that even if there's no window/focused handle we still have something to dispatch to. Co-authored-by: Antonio --- crates/editor2/src/element.rs | 63 ++++++----- crates/gpui2/src/app.rs | 6 +- crates/gpui2/src/elements/div.rs | 6 +- crates/gpui2/src/key_dispatch.rs | 25 +++-- crates/gpui2/src/window.rs | 186 +++++++++++++++++-------------- 5 files changed, 158 insertions(+), 128 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index d7b9d0bb40498cd8fb6c51f4cd33d5e6489f4ad1..ab11f5ffb5427d39905675756be5d9996e984fc1 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2803,35 +2803,46 @@ impl Element for EditorElement { let focus_handle = editor.focus_handle(cx); let dispatch_context = self.editor.read(cx).dispatch_context(cx); - cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| { - self.register_actions(cx); - self.register_key_listeners(cx); - - // We call with_z_index to establish a new stacking context. - cx.with_z_index(0, |cx| { - cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor - // take precedence. - cx.with_z_index(0, |cx| { - self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); - }); - let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); - cx.handle_input(&focus_handle, input_handler); + cx.with_key_dispatch( + Some(dispatch_context), + Some(focus_handle.clone()), + |_, cx| { + self.register_actions(cx); + self.register_key_listeners(cx); + + // We call with_z_index to establish a new stacking context. + cx.with_z_index(0, |cx| { + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor + // take precedence. + cx.with_z_index(0, |cx| { + self.paint_mouse_listeners( + bounds, + gutter_bounds, + text_bounds, + &layout, + cx, + ); + }); + let input_handler = + ElementInputHandler::new(bounds, self.editor.clone(), cx); + cx.handle_input(&focus_handle, input_handler); - self.paint_background(gutter_bounds, text_bounds, &layout, cx); - if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &mut layout, cx); - } - self.paint_text(text_bounds, &mut layout, cx); + self.paint_background(gutter_bounds, text_bounds, &layout, cx); + if layout.gutter_size.width > Pixels::ZERO { + self.paint_gutter(gutter_bounds, &mut layout, cx); + } + self.paint_text(text_bounds, &mut layout, cx); - if !layout.blocks.is_empty() { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.paint_blocks(bounds, &mut layout, cx); - }) - } + if !layout.blocks.is_empty() { + cx.with_element_id(Some("editor_blocks"), |cx| { + self.paint_blocks(bounds, &mut layout, cx); + }) + } + }); }); - }); - }) + }, + ) } } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index fec6f150f6c341f916e0173379aba63bebcc1ffd..4803eb8b97c8cf86bfb56843f739670e1f81cb52 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -201,7 +201,7 @@ pub struct AppContext { pub(crate) windows: SlotMap>, pub(crate) keymap: Arc>, pub(crate) global_action_listeners: - HashMap>>, + HashMap>>, pending_effects: VecDeque, pub(crate) pending_notifications: HashSet, pub(crate) pending_global_notifications: HashSet, @@ -962,9 +962,9 @@ impl AppContext { self.global_action_listeners .entry(TypeId::of::()) .or_default() - .push(Box::new(move |action, phase, cx| { + .push(Rc::new(move |action, phase, cx| { if phase == DispatchPhase::Bubble { - let action = action.as_any().downcast_ref().unwrap(); + let action = action.downcast_ref().unwrap(); listener(action, cx) } })); diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index ce457fc6931246ee1e2e4d19a4a1639b37998395..c95a7f890f986b19ba7fdbac6b35c36ed8db36df 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -55,7 +55,7 @@ pub trait InteractiveElement: Sized + Element { E: Debug, { if let Some(key_context) = key_context.try_into().log_err() { - self.interactivity().key_context = key_context; + self.interactivity().key_context = Some(key_context); } self } @@ -722,7 +722,7 @@ impl DivState { pub struct Interactivity { pub element_id: Option, - pub key_context: KeyContext, + pub key_context: Option, pub focusable: bool, pub tracked_focus_handle: Option, pub scroll_handle: Option, @@ -1238,7 +1238,7 @@ impl Default for Interactivity { fn default() -> Self { Self { element_id: None, - key_context: KeyContext::default(), + key_context: None, focusable: false, tracked_focus_handle: None, scroll_handle: None, diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 4838b1a612ce65ba33c03ac25da878a752f716d3..a79a358a1c38453fe7a8501e1ae0cae5ccf832c3 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -61,7 +61,7 @@ impl DispatchTree { self.keystroke_matchers.clear(); } - pub fn push_node(&mut self, context: KeyContext) { + pub fn push_node(&mut self, context: Option) { let parent = self.node_stack.last().copied(); let node_id = DispatchNodeId(self.nodes.len()); self.nodes.push(DispatchNode { @@ -69,7 +69,7 @@ impl DispatchTree { ..Default::default() }); self.node_stack.push(node_id); - if !context.is_empty() { + if let Some(context) = context { self.active_node().context = context.clone(); self.context_stack.push(context); } @@ -148,16 +148,14 @@ impl DispatchTree { false } - pub fn available_actions(&self, target: FocusId) -> Vec> { + pub fn available_actions(&self, target: DispatchNodeId) -> Vec> { let mut actions = Vec::new(); - if let Some(node) = self.focusable_node_ids.get(&target) { - for node_id in self.dispatch_path(*node) { - let node = &self.nodes[node_id.0]; - for DispatchActionListener { action_type, .. } in &node.action_listeners { - // Intentionally silence these errors without logging. - // If an action cannot be built by default, it's not available. - actions.extend(self.action_registry.build_action_type(action_type).ok()); - } + for node_id in self.dispatch_path(target) { + let node = &self.nodes[node_id.0]; + for DispatchActionListener { action_type, .. } in &node.action_listeners { + // Intentionally silence these errors without logging. + // If an action cannot be built by default, it's not available. + actions.extend(self.action_registry.build_action_type(action_type).ok()); } } actions @@ -236,6 +234,11 @@ impl DispatchTree { self.focusable_node_ids.get(&target).copied() } + pub fn root_node_id(&self) -> DispatchNodeId { + debug_assert!(!self.nodes.is_empty()); + DispatchNodeId(0) + } + fn active_node_id(&self) -> DispatchNodeId { *self.node_stack.last().unwrap() } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 271b09b8b950d777400f403fa59d5b81ad4e5ed6..6323eb962f101daa66fcfa8ae1482408507b7670 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -453,19 +453,21 @@ impl<'a> WindowContext<'a> { } pub fn dispatch_action(&mut self, action: Box) { - if let Some(focus_handle) = self.focused() { - self.defer(move |cx| { - if let Some(node_id) = cx - .window - .current_frame - .dispatch_tree - .focusable_node_id(focus_handle.id) - { - cx.propagate_event = true; - cx.dispatch_action_on_node(node_id, action); - } - }) - } + let focus_handle = self.focused(); + + self.defer(move |cx| { + let node_id = focus_handle + .and_then(|handle| { + cx.window + .current_frame + .dispatch_tree + .focusable_node_id(handle.id) + }) + .unwrap_or_else(|| cx.window.current_frame.dispatch_tree.root_node_id()); + + cx.propagate_event = true; + cx.dispatch_action_on_node(node_id, action); + }) } /// Schedules the given function to be run at the end of the current effect cycle, allowing entities @@ -1154,8 +1156,19 @@ impl<'a> WindowContext<'a> { self.start_frame(); self.with_z_index(0, |cx| { - let available_space = cx.window.viewport_size.map(Into::into); - root_view.draw(Point::zero(), available_space, cx); + cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { + for (action_type, action_listeners) in &cx.app.global_action_listeners { + for action_listener in action_listeners.iter().cloned() { + cx.window.current_frame.dispatch_tree.on_action( + *action_type, + Rc::new(move |action, phase, cx| action_listener(action, phase, cx)), + ) + } + } + + let available_space = cx.window.viewport_size.map(Into::into); + root_view.draw(Point::zero(), available_space, cx); + }) }); if let Some(active_drag) = self.app.active_drag.take() { @@ -1338,73 +1351,77 @@ impl<'a> WindowContext<'a> { } fn dispatch_key_event(&mut self, event: &dyn Any) { - if let Some(node_id) = self.window.focus.and_then(|focus_id| { - self.window - .current_frame - .dispatch_tree - .focusable_node_id(focus_id) - }) { - let dispatch_path = self - .window - .current_frame - .dispatch_tree - .dispatch_path(node_id); + let node_id = self + .window + .focus + .and_then(|focus_id| { + self.window + .current_frame + .dispatch_tree + .focusable_node_id(focus_id) + }) + .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id()); - let mut actions: Vec> = Vec::new(); + let dispatch_path = self + .window + .current_frame + .dispatch_tree + .dispatch_path(node_id); - // Capture phase - let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new(); - self.propagate_event = true; + let mut actions: Vec> = Vec::new(); - for node_id in &dispatch_path { - let node = self.window.current_frame.dispatch_tree.node(*node_id); + // Capture phase + let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new(); + self.propagate_event = true; - if !node.context.is_empty() { - context_stack.push(node.context.clone()); - } + for node_id in &dispatch_path { + let node = self.window.current_frame.dispatch_tree.node(*node_id); - for key_listener in node.key_listeners.clone() { - key_listener(event, DispatchPhase::Capture, self); - if !self.propagate_event { - return; - } + if !node.context.is_empty() { + context_stack.push(node.context.clone()); + } + + for key_listener in node.key_listeners.clone() { + key_listener(event, DispatchPhase::Capture, self); + if !self.propagate_event { + return; } } + } - // Bubble phase - for node_id in dispatch_path.iter().rev() { - // Handle low level key events - let node = self.window.current_frame.dispatch_tree.node(*node_id); - for key_listener in node.key_listeners.clone() { - key_listener(event, DispatchPhase::Bubble, self); - if !self.propagate_event { - return; - } + // Bubble phase + for node_id in dispatch_path.iter().rev() { + // Handle low level key events + let node = self.window.current_frame.dispatch_tree.node(*node_id); + for key_listener in node.key_listeners.clone() { + key_listener(event, DispatchPhase::Bubble, self); + if !self.propagate_event { + return; } + } - // Match keystrokes - let node = self.window.current_frame.dispatch_tree.node(*node_id); - if !node.context.is_empty() { - if let Some(key_down_event) = event.downcast_ref::() { - if let Some(found) = self - .window - .current_frame - .dispatch_tree - .dispatch_key(&key_down_event.keystroke, &context_stack) - { - actions.push(found.boxed_clone()) - } + // Match keystrokes + let node = self.window.current_frame.dispatch_tree.node(*node_id); + if !node.context.is_empty() { + if let Some(key_down_event) = event.downcast_ref::() { + if let Some(found) = self + .window + .current_frame + .dispatch_tree + .dispatch_key(&key_down_event.keystroke, &context_stack) + { + actions.push(found.boxed_clone()) } - - context_stack.pop(); } + + context_stack.pop(); } + } - for action in actions { - self.dispatch_action_on_node(node_id, action); - if !self.propagate_event { - return; - } + for action in actions { + self.dispatch_action_on_node(node_id, action); + if !self.propagate_event { + return; } } } @@ -1490,22 +1507,21 @@ impl<'a> WindowContext<'a> { } pub fn available_actions(&self) -> Vec> { - if let Some(focus_id) = self.window.focus { - let mut actions = self - .window - .current_frame - .dispatch_tree - .available_actions(focus_id); - actions.extend( - self.app - .global_action_listeners - .keys() - .filter_map(|type_id| self.app.actions.build_action_type(type_id).ok()), - ); - actions - } else { - Vec::new() - } + let node_id = self + .window + .focus + .and_then(|focus_id| { + self.window + .current_frame + .dispatch_tree + .focusable_node_id(focus_id) + }) + .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id()); + + self.window + .current_frame + .dispatch_tree + .available_actions(node_id) } pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { @@ -1561,7 +1577,7 @@ impl<'a> WindowContext<'a> { //========== ELEMENT RELATED FUNCTIONS =========== pub fn with_key_dispatch( &mut self, - context: KeyContext, + context: Option, focus_handle: Option, f: impl FnOnce(Option, &mut Self) -> R, ) -> R { From f833cd7c160239b9f72e1ca394cdd6209e35ede2 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 6 Dec 2023 10:41:49 -0500 Subject: [PATCH 15/23] Use specified color for non-highlighted text in `HighlightedLabel` (#3509) This PR fixes an issue where the specified color for a `HighlightedLabel` was not respected as the default color for non-highlighted text. Release Notes: - N/A --- crates/ui2/src/components/label.rs | 5 ++++- crates/ui2/src/components/stories/label.rs | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/ui2/src/components/label.rs b/crates/ui2/src/components/label.rs index 7aeda3e850fbe91463f6f3f6a0298cb3fc7fa5a7..d455a041ee6e8f861a3cdacf1c155c2f7fc57871 100644 --- a/crates/ui2/src/components/label.rs +++ b/crates/ui2/src/components/label.rs @@ -129,6 +129,9 @@ impl RenderOnce for HighlightedLabel { )); } + let mut text_style = cx.text_style().clone(); + text_style.color = self.color.color(cx); + div() .flex() .when(self.strikethrough, |this| { @@ -146,7 +149,7 @@ impl RenderOnce for HighlightedLabel { LabelSize::Default => this.text_ui(), LabelSize::Small => this.text_ui_sm(), }) - .child(StyledText::new(self.label).with_highlights(&cx.text_style(), highlights)) + .child(StyledText::new(self.label).with_highlights(&text_style, highlights)) } } diff --git a/crates/ui2/src/components/stories/label.rs b/crates/ui2/src/components/stories/label.rs index 2417bee6e1cd77646ceddd2c4febe4c6e45b7e19..e026d388fde62545f47aab22985fa7efba4f3c7a 100644 --- a/crates/ui2/src/components/stories/label.rs +++ b/crates/ui2/src/components/stories/label.rs @@ -23,5 +23,9 @@ impl Render for LabelStory { "Héllo, world!", vec![0, 1, 3, 8, 9, 13], )) + .child(Story::label("Highlighted with `color`")) + .child( + HighlightedLabel::new("Hello, world!", vec![0, 1, 2, 7, 8, 12]).color(Color::Error), + ) } } From 6549a9a091512f6db53e08efbd3a94d53d524f00 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:52:52 +0100 Subject: [PATCH 16/23] Let WindowContext::dispatch_action handle global actions Co-authored-by: Antonio --- crates/gpui2/src/app.rs | 82 ++++++++++++++------------------ crates/gpui2/src/key_dispatch.rs | 20 ++++---- crates/gpui2/src/window.rs | 16 +++++++ 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 0abdf4b17410419529c705bfd808271f5100a7c0..d23d6e3d9d15003ab3236a6d491143f25d1f48de 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -1040,20 +1040,10 @@ impl AppContext { pub fn is_action_available(&mut self, action: &dyn Action) -> bool { if let Some(window) = self.active_window() { - let window_action_available = window - .update(self, |_, cx| { - if let Some(focus_id) = cx.window.focus { - cx.window - .current_frame - .dispatch_tree - .is_action_available(action, focus_id) - } else { - false - } - }) - .unwrap_or(false); - if window_action_available { - return true; + if let Ok(window_action_available) = + window.update(self, |_, cx| cx.is_action_available(action)) + { + return window_action_available; } } @@ -1075,44 +1065,19 @@ impl AppContext { } pub fn dispatch_action(&mut self, action: &dyn Action) { - self.propagate_event = true; - - if let Some(mut global_listeners) = self - .global_action_listeners - .remove(&action.as_any().type_id()) - { - for listener in &global_listeners { - listener(action, DispatchPhase::Capture, self); - if !self.propagate_event { - break; - } - } - - global_listeners.extend( - self.global_action_listeners - .remove(&action.as_any().type_id()) - .unwrap_or_default(), - ); - - self.global_action_listeners - .insert(action.as_any().type_id(), global_listeners); - } - - if self.propagate_event { - if let Some(active_window) = self.active_window() { - active_window - .update(self, |_, cx| cx.dispatch_action(action.boxed_clone())) - .log_err(); - } - } + if let Some(active_window) = self.active_window() { + active_window + .update(self, |_, cx| cx.dispatch_action(action.boxed_clone())) + .log_err(); + } else { + self.propagate_event = true; - if self.propagate_event { if let Some(mut global_listeners) = self .global_action_listeners .remove(&action.as_any().type_id()) { - for listener in global_listeners.iter().rev() { - listener(action, DispatchPhase::Bubble, self); + for listener in &global_listeners { + listener(action.as_any(), DispatchPhase::Capture, self); if !self.propagate_event { break; } @@ -1127,6 +1092,29 @@ impl AppContext { self.global_action_listeners .insert(action.as_any().type_id(), global_listeners); } + + if self.propagate_event { + if let Some(mut global_listeners) = self + .global_action_listeners + .remove(&action.as_any().type_id()) + { + for listener in global_listeners.iter().rev() { + listener(action.as_any(), DispatchPhase::Bubble, self); + if !self.propagate_event { + break; + } + } + + global_listeners.extend( + self.global_action_listeners + .remove(&action.as_any().type_id()) + .unwrap_or_default(), + ); + + self.global_action_listeners + .insert(action.as_any().type_id(), global_listeners); + } + } } } } diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 07561644f7a36164aa328349aaf7c08ad2c74621..80e662ad3ef6477b4cd0d925b2852957a4c04d00 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -161,17 +161,15 @@ impl DispatchTree { actions } - pub fn is_action_available(&self, action: &dyn Action, target: FocusId) -> bool { - if let Some(node) = self.focusable_node_ids.get(&target) { - for node_id in self.dispatch_path(*node) { - let node = &self.nodes[node_id.0]; - if node - .action_listeners - .iter() - .any(|listener| listener.action_type == action.as_any().type_id()) - { - return true; - } + pub fn is_action_available(&self, action: &dyn Action, target: DispatchNodeId) -> bool { + for node_id in self.dispatch_path(target) { + let node = &self.nodes[node_id.0]; + if node + .action_listeners + .iter() + .any(|listener| listener.action_type == action.as_any().type_id()) + { + return true; } } false diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 1bd8b4653797669519349a0edb6fb783c361c392..8995d04b64de4abac4653e228691f3784eb89a6e 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -804,6 +804,22 @@ impl<'a> WindowContext<'a> { ); } + pub fn is_action_available(&self, action: &dyn Action) -> bool { + let target = self + .focused() + .and_then(|focused_handle| { + self.window + .current_frame + .dispatch_tree + .focusable_node_id(focused_handle.id) + }) + .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id()); + self.window + .current_frame + .dispatch_tree + .is_action_available(action, target) + } + /// The position of the mouse relative to the window. pub fn mouse_position(&self) -> Point { self.window.mouse_position From 8f1c74b8bc0cee150b33b76309b9f21735bfda4f Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 6 Dec 2023 11:17:12 -0500 Subject: [PATCH 17/23] Factor out `LabelLike` to share common label styles (#3510) This PR factors out a new `LabelLike` component to share common styles between the `Label` and `HighlightedLabel` components. Release Notes: - N/A --- crates/copilot2/src/sign_in.rs | 2 +- crates/diagnostics2/src/diagnostics.rs | 2 +- crates/editor2/src/items.rs | 2 +- crates/go_to_line2/src/go_to_line.rs | 2 +- crates/ui2/src/components/label.rs | 188 +----------------- .../src/components/label/highlighted_label.rs | 86 ++++++++ crates/ui2/src/components/label/label.rs | 48 +++++ crates/ui2/src/components/label/label_like.rs | 102 ++++++++++ crates/ui2/src/prelude.rs | 2 +- 9 files changed, 247 insertions(+), 187 deletions(-) create mode 100644 crates/ui2/src/components/label/highlighted_label.rs create mode 100644 crates/ui2/src/components/label/label.rs create mode 100644 crates/ui2/src/components/label/label_like.rs diff --git a/crates/copilot2/src/sign_in.rs b/crates/copilot2/src/sign_in.rs index 8da34c427f9eb98c4b2bee031761b4304b498f13..4fa93ffcf89a496ae1b150334c9b7a310b779cec 100644 --- a/crates/copilot2/src/sign_in.rs +++ b/crates/copilot2/src/sign_in.rs @@ -5,7 +5,7 @@ use gpui::{ WindowBounds, WindowHandle, WindowKind, WindowOptions, }; use theme::ActiveTheme; -use ui::{h_stack, v_stack, Button, Clickable, Color, Icon, IconElement, Label}; +use ui::{prelude::*, Button, Icon, IconElement, Label}; const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; diff --git a/crates/diagnostics2/src/diagnostics.rs b/crates/diagnostics2/src/diagnostics.rs index 44acc285e8231a48e20c791f38f09f6619e99d08..f725fb7c4f58906f4263a5bcc86e6dd1ec24af6f 100644 --- a/crates/diagnostics2/src/diagnostics.rs +++ b/crates/diagnostics2/src/diagnostics.rs @@ -36,7 +36,7 @@ use std::{ }; use theme::ActiveTheme; pub use toolbar_controls::ToolbarControls; -use ui::{h_stack, Color, HighlightedLabel, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, HighlightedLabel, Icon, IconElement, Label}; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index 70d4d6bf255fad8b056a19ba5d5b41dc97a66178..12feb31696922558d61eb00d0a995039a21b2cea 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -32,7 +32,7 @@ use std::{ }; use text::Selection; use theme::{ActiveTheme, Theme}; -use ui::{h_stack, Color, Label}; +use ui::{h_stack, prelude::*, Label}; use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt}; use workspace::{ item::{BreadcrumbText, FollowEvent, FollowableItemHandle}, diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 5ad95c1f6ea6cf0fb49e185a044d8be7eaf383fe..aff9942c265da765431460d9f381abf131e3573a 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -6,7 +6,7 @@ use gpui::{ }; use text::{Bias, Point}; use theme::ActiveTheme; -use ui::{h_stack, v_stack, Color, Label, StyledExt}; +use ui::{h_stack, prelude::*, v_stack, Label}; use util::paths::FILE_ROW_COLUMN_DELIMITER; actions!(Toggle); diff --git a/crates/ui2/src/components/label.rs b/crates/ui2/src/components/label.rs index d455a041ee6e8f861a3cdacf1c155c2f7fc57871..bda97be6490d20309941e9de980f4a0fb2350bb1 100644 --- a/crates/ui2/src/components/label.rs +++ b/crates/ui2/src/components/label.rs @@ -1,183 +1,7 @@ -use std::ops::Range; +mod highlighted_label; +mod label; +mod label_like; -use crate::prelude::*; -use crate::styled_ext::StyledExt; -use gpui::{relative, Div, HighlightStyle, IntoElement, StyledText, WindowContext}; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] -pub enum LabelSize { - #[default] - Default, - Small, -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum LineHeightStyle { - #[default] - TextLabel, - /// Sets the line height to 1 - UILabel, -} - -#[derive(IntoElement, Clone)] -pub struct Label { - label: SharedString, - size: LabelSize, - line_height_style: LineHeightStyle, - color: Color, - strikethrough: bool, -} - -impl RenderOnce for Label { - type Rendered = Div; - - fn render(self, cx: &mut WindowContext) -> Self::Rendered { - div() - .when(self.strikethrough, |this| { - this.relative().child( - div() - .absolute() - .top_1_2() - .w_full() - .h_px() - .bg(Color::Hidden.color(cx)), - ) - }) - .map(|this| match self.size { - LabelSize::Default => this.text_ui(), - LabelSize::Small => this.text_ui_sm(), - }) - .when(self.line_height_style == LineHeightStyle::UILabel, |this| { - this.line_height(relative(1.)) - }) - .text_color(self.color.color(cx)) - .child(self.label.clone()) - } -} - -impl Label { - pub fn new(label: impl Into) -> Self { - Self { - label: label.into(), - size: LabelSize::Default, - line_height_style: LineHeightStyle::default(), - color: Color::Default, - strikethrough: false, - } - } - - pub fn size(mut self, size: LabelSize) -> Self { - self.size = size; - self - } - - pub fn color(mut self, color: Color) -> Self { - self.color = color; - self - } - - pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { - self.line_height_style = line_height_style; - self - } - - pub fn set_strikethrough(mut self, strikethrough: bool) -> Self { - self.strikethrough = strikethrough; - self - } -} - -#[derive(IntoElement)] -pub struct HighlightedLabel { - label: SharedString, - size: LabelSize, - color: Color, - highlight_indices: Vec, - strikethrough: bool, -} - -impl RenderOnce for HighlightedLabel { - type Rendered = Div; - - fn render(self, cx: &mut WindowContext) -> Self::Rendered { - let highlight_color = cx.theme().colors().text_accent; - - let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); - let mut highlights: Vec<(Range, HighlightStyle)> = Vec::new(); - - while let Some(start_ix) = highlight_indices.next() { - let mut end_ix = start_ix; - - 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; - } - - highlights.push(( - start_ix..end_ix, - HighlightStyle { - color: Some(highlight_color), - ..Default::default() - }, - )); - } - - let mut text_style = cx.text_style().clone(); - text_style.color = self.color.color(cx); - - div() - .flex() - .when(self.strikethrough, |this| { - this.relative().child( - div() - .absolute() - .top_px() - .my_auto() - .w_full() - .h_px() - .bg(Color::Hidden.color(cx)), - ) - }) - .map(|this| match self.size { - LabelSize::Default => this.text_ui(), - LabelSize::Small => this.text_ui_sm(), - }) - .child(StyledText::new(self.label).with_highlights(&text_style, highlights)) - } -} - -impl HighlightedLabel { - /// shows a label with the given characters highlighted. - /// characters are identified by utf8 byte position. - pub fn new(label: impl Into, highlight_indices: Vec) -> Self { - Self { - label: label.into(), - size: LabelSize::Default, - color: Color::Default, - highlight_indices, - strikethrough: false, - } - } - - pub fn size(mut self, size: LabelSize) -> Self { - self.size = size; - self - } - - pub fn color(mut self, color: Color) -> Self { - self.color = color; - self - } - - pub fn set_strikethrough(mut self, strikethrough: bool) -> Self { - self.strikethrough = strikethrough; - self - } -} +pub use highlighted_label::*; +pub use label::*; +pub use label_like::*; diff --git a/crates/ui2/src/components/label/highlighted_label.rs b/crates/ui2/src/components/label/highlighted_label.rs new file mode 100644 index 0000000000000000000000000000000000000000..a7fbb0d8167cd0f78fd40f01454621a3e26ca33d --- /dev/null +++ b/crates/ui2/src/components/label/highlighted_label.rs @@ -0,0 +1,86 @@ +use std::ops::Range; + +use gpui::{HighlightStyle, StyledText}; + +use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; + +#[derive(IntoElement)] +pub struct HighlightedLabel { + base: LabelLike, + label: SharedString, + highlight_indices: Vec, +} + +impl HighlightedLabel { + /// Constructs a label with the given characters highlighted. + /// Characters are identified by UTF-8 byte position. + pub fn new(label: impl Into, highlight_indices: Vec) -> Self { + Self { + base: LabelLike::new(), + label: label.into(), + highlight_indices, + } + } +} + +impl LabelCommon for HighlightedLabel { + fn size(mut self, size: LabelSize) -> Self { + self.base = self.base.size(size); + self + } + + fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { + self.base = self.base.line_height_style(line_height_style); + self + } + + fn color(mut self, color: Color) -> Self { + self.base = self.base.color(color); + self + } + + fn strikethrough(mut self, strikethrough: bool) -> Self { + self.base = self.base.strikethrough(strikethrough); + self + } +} + +impl RenderOnce for HighlightedLabel { + type Rendered = LabelLike; + + fn render(self, cx: &mut WindowContext) -> Self::Rendered { + let highlight_color = cx.theme().colors().text_accent; + + let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); + let mut highlights: Vec<(Range, HighlightStyle)> = Vec::new(); + + while let Some(start_ix) = highlight_indices.next() { + let mut end_ix = start_ix; + + 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; + } + + highlights.push(( + start_ix..end_ix, + HighlightStyle { + color: Some(highlight_color), + ..Default::default() + }, + )); + } + + let mut text_style = cx.text_style().clone(); + text_style.color = self.base.color.color(cx); + + LabelLike::new().child(StyledText::new(self.label).with_highlights(&text_style, highlights)) + } +} diff --git a/crates/ui2/src/components/label/label.rs b/crates/ui2/src/components/label/label.rs new file mode 100644 index 0000000000000000000000000000000000000000..8272340888837ca3f2ec2a0294a3168c14009cd0 --- /dev/null +++ b/crates/ui2/src/components/label/label.rs @@ -0,0 +1,48 @@ +use gpui::WindowContext; + +use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; + +#[derive(IntoElement)] +pub struct Label { + base: LabelLike, + label: SharedString, +} + +impl Label { + pub fn new(label: impl Into) -> Self { + Self { + base: LabelLike::new(), + label: label.into(), + } + } +} + +impl LabelCommon for Label { + fn size(mut self, size: LabelSize) -> Self { + self.base = self.base.size(size); + self + } + + fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { + self.base = self.base.line_height_style(line_height_style); + self + } + + fn color(mut self, color: Color) -> Self { + self.base = self.base.color(color); + self + } + + fn strikethrough(mut self, strikethrough: bool) -> Self { + self.base = self.base.strikethrough(strikethrough); + self + } +} + +impl RenderOnce for Label { + type Rendered = LabelLike; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + self.base.child(self.label) + } +} diff --git a/crates/ui2/src/components/label/label_like.rs b/crates/ui2/src/components/label/label_like.rs new file mode 100644 index 0000000000000000000000000000000000000000..72a48adea451f49cd10dd2e2bd224f16d4ae51a9 --- /dev/null +++ b/crates/ui2/src/components/label/label_like.rs @@ -0,0 +1,102 @@ +use gpui::{relative, AnyElement, Div, Styled}; +use smallvec::SmallVec; + +use crate::prelude::*; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] +pub enum LabelSize { + #[default] + Default, + Small, +} + +#[derive(Default, PartialEq, Copy, Clone)] +pub enum LineHeightStyle { + #[default] + TextLabel, + /// Sets the line height to 1 + UILabel, +} + +pub trait LabelCommon { + fn size(self, size: LabelSize) -> Self; + fn line_height_style(self, line_height_style: LineHeightStyle) -> Self; + fn color(self, color: Color) -> Self; + fn strikethrough(self, strikethrough: bool) -> Self; +} + +#[derive(IntoElement)] +pub struct LabelLike { + size: LabelSize, + line_height_style: LineHeightStyle, + pub(crate) color: Color, + strikethrough: bool, + children: SmallVec<[AnyElement; 2]>, +} + +impl LabelLike { + pub fn new() -> Self { + Self { + size: LabelSize::Default, + line_height_style: LineHeightStyle::default(), + color: Color::Default, + strikethrough: false, + children: SmallVec::new(), + } + } +} + +impl LabelCommon for LabelLike { + fn size(mut self, size: LabelSize) -> Self { + self.size = size; + self + } + + fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { + self.line_height_style = line_height_style; + self + } + + fn color(mut self, color: Color) -> Self { + self.color = color; + self + } + + fn strikethrough(mut self, strikethrough: bool) -> Self { + self.strikethrough = strikethrough; + self + } +} + +impl ParentElement for LabelLike { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + +impl RenderOnce for LabelLike { + type Rendered = Div; + + fn render(self, cx: &mut WindowContext) -> Self::Rendered { + div() + .when(self.strikethrough, |this| { + this.relative().child( + div() + .absolute() + .top_1_2() + .w_full() + .h_px() + .bg(Color::Hidden.color(cx)), + ) + }) + .map(|this| match self.size { + LabelSize::Default => this.text_ui(), + LabelSize::Small => this.text_ui_sm(), + }) + .when(self.line_height_style == LineHeightStyle::UILabel, |this| { + this.line_height(relative(1.)) + }) + .text_color(self.color.color(cx)) + .children(self.children) + } +} diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index 38065b62754b5facb5ed9440ad2b74535f40d445..a71efa4dc3ebc8e4065260575beac78972f3f89c 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -9,5 +9,5 @@ pub use crate::disableable::*; pub use crate::fixed::*; pub use crate::selectable::*; pub use crate::{h_stack, v_stack}; -pub use crate::{ButtonCommon, Color, StyledExt}; +pub use crate::{ButtonCommon, Color, LabelCommon, StyledExt}; pub use theme::ActiveTheme; From c8ddc95caa4860948fd6f9a7dc7801cc828a6b64 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 6 Dec 2023 17:26:54 +0100 Subject: [PATCH 18/23] Take a `Keymap` when setting app menus For a brief period on this branch, we were taking a `DispatchTree`. Doing so resulted in more accurate key bindings but it meant that we would have had to recompute the app menus every time the key context changed. We decided to err on the side of keeping things simple and work in the same way they worked back in zed1. Co-Authored-By: Marshall --- crates/gpui2/src/app.rs | 11 +---------- crates/gpui2/src/platform.rs | 6 +++--- crates/gpui2/src/platform/mac/platform.rs | 23 ++++++++++------------ crates/gpui2/src/platform/test/platform.rs | 7 +++---- 4 files changed, 17 insertions(+), 30 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index d23d6e3d9d15003ab3236a6d491143f25d1f48de..0715ace9eaf8ca0de7fabcb635264e361fc09dad 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -1052,16 +1052,7 @@ impl AppContext { } pub fn set_menus(&mut self, menus: Vec) { - if let Some(active_window) = self.active_window() { - active_window - .update(self, |_, cx| { - cx.platform - .set_menus(menus, Some(&cx.window.current_frame.dispatch_tree)); - }) - .ok(); - } else { - self.platform.set_menus(menus, None); - } + self.platform.set_menus(menus, &self.keymap.lock()); } pub fn dispatch_action(&mut self, action: &dyn Action) { diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 7bcd91a5e07dbc03a52fedd6837b9e06c542c6f4..66cf7c14efb95ad520083122a900f9ff900c7e94 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -6,8 +6,8 @@ mod mac; mod test; use crate::{ - point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, DispatchTree, - Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, + point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, + FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, TaskLabel, }; @@ -92,7 +92,7 @@ pub(crate) trait Platform: 'static { fn on_reopen(&self, callback: Box); fn on_event(&self, callback: Box bool>); - fn set_menus(&self, menus: Vec, dispatch_tree: Option<&DispatchTree>); + fn set_menus(&self, menus: Vec, keymap: &Keymap); fn on_app_menu_action(&self, callback: Box); fn on_will_open_app_menu(&self, callback: Box); fn on_validate_app_menu_command(&self, callback: Box bool>); diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 8a5ee676f7c9e76364e6aea27a9b064a917cca36..2deea545e164b3445dcd6388048eaf97b66e9102 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -1,7 +1,7 @@ use super::{events::key_to_native, BoolExt}; use crate::{ - Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DispatchTree, - DisplayId, ForegroundExecutor, InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, + Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, + ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions, }; @@ -206,7 +206,7 @@ impl MacPlatform { menus: Vec, delegate: id, actions: &mut Vec>, - dispatch_tree: Option<&DispatchTree>, + keymap: &Keymap, ) -> id { let application_menu = NSMenu::new(nil).autorelease(); application_menu.setDelegate_(delegate); @@ -217,7 +217,7 @@ impl MacPlatform { menu.setDelegate_(delegate); for item_config in menu_config.items { - menu.addItem_(self.create_menu_item(item_config, delegate, actions, dispatch_tree)); + menu.addItem_(self.create_menu_item(item_config, delegate, actions, keymap)); } let menu_item = NSMenuItem::new(nil).autorelease(); @@ -238,7 +238,7 @@ impl MacPlatform { item: MenuItem, delegate: id, actions: &mut Vec>, - dispatch_tree: Option<&DispatchTree>, + keymap: &Keymap, ) -> id { match item { MenuItem::Separator => NSMenuItem::separatorItem(nil), @@ -247,11 +247,8 @@ impl MacPlatform { action, os_action, } => { - let bindings = dispatch_tree - .map(|tree| tree.bindings_for_action(action.as_ref(), &tree.context_stack)) - .unwrap_or_default(); - let keystrokes = bindings - .iter() + let keystrokes = keymap + .bindings_for_action(action.type_id()) .find(|binding| binding.action().partial_eq(action.as_ref())) .map(|binding| binding.keystrokes()); @@ -343,7 +340,7 @@ impl MacPlatform { let submenu = NSMenu::new(nil).autorelease(); submenu.setDelegate_(delegate); for item in items { - submenu.addItem_(self.create_menu_item(item, delegate, actions, dispatch_tree)); + submenu.addItem_(self.create_menu_item(item, delegate, actions, keymap)); } item.setSubmenu_(submenu); item.setTitle_(ns_string(name)); @@ -691,12 +688,12 @@ impl Platform for MacPlatform { } } - fn set_menus(&self, menus: Vec, dispatch_tree: Option<&DispatchTree>) { + fn set_menus(&self, menus: Vec, keymap: &Keymap) { unsafe { let app: id = msg_send![APP_CLASS, sharedApplication]; let mut state = self.0.lock(); let actions = &mut state.menu_actions; - app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), actions, dispatch_tree)); + app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), actions, keymap)); } } diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index c76796b5225f9e67ac51fe0898ffef975fc02cad..10fd9f0ff38e7abb57a11d201c9745371cef3f49 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -1,7 +1,6 @@ use crate::{ - AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DispatchTree, DisplayId, - ForegroundExecutor, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, - WindowOptions, + AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, + Keymap, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions, }; use anyhow::{anyhow, Result}; use collections::VecDeque; @@ -213,7 +212,7 @@ impl Platform for TestPlatform { unimplemented!() } - fn set_menus(&self, _menus: Vec, _dispatch_tree: Option<&DispatchTree>) { + fn set_menus(&self, _menus: Vec, _keymap: &Keymap) { unimplemented!() } From 886ec79d587ffa00b0d96f14f5114dac8c2e8124 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 6 Dec 2023 17:45:59 +0100 Subject: [PATCH 19/23] Make TestPlatform::set_menus a no-op --- crates/gpui2/src/platform/test/platform.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 10fd9f0ff38e7abb57a11d201c9745371cef3f49..edbe39480994a9425f733ea5a5ed967f63535f11 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -212,9 +212,7 @@ impl Platform for TestPlatform { unimplemented!() } - fn set_menus(&self, _menus: Vec, _keymap: &Keymap) { - unimplemented!() - } + fn set_menus(&self, _menus: Vec, _keymap: &Keymap) {} fn on_app_menu_action(&self, _callback: Box) { unimplemented!() From 80c8fd1f4c09ec049ae2b96fa0aa584e3d70fed2 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 6 Dec 2023 11:54:59 -0500 Subject: [PATCH 20/23] Fix toolbar not appearing for initial pane (#3512) This PR fixes an issues where the toolbar would not appear for the center pane when Zed2 initially loads. We resolved this by adding a call to initialize the center pane when the workspace is initialized Due to changes in the way subscriptions work we can on longer observe an event that is emitted in the same event cycle in which the subscription is created. Because of this we need to explicitly initialize the center pane, as it won't get performed by the subscription. Release Notes: - N/A --------- Co-authored-by: Antonio --- crates/zed2/src/zed2.rs | 64 ++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 5f2099154cc8f298baedceeee3ab282fe8eca079..daa25b8eb95948c2aa281831529c20bd07c6d2d8 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -11,7 +11,7 @@ use breadcrumbs::Breadcrumbs; use collections::VecDeque; use editor::{Editor, MultiBuffer}; use gpui::{ - actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions, + actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions, View, ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions, }; pub use only_instance::*; @@ -30,6 +30,7 @@ use util::{ ResultExt, }; use uuid::Uuid; +use workspace::Pane; use workspace::{ create_and_open_local_file, dock::PanelHandle, notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile, @@ -92,37 +93,12 @@ pub fn build_window_options( pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { cx.observe_new_views(move |workspace: &mut Workspace, cx| { let workspace_handle = cx.view().clone(); + let center_pane = workspace.active_pane().clone(); + initialize_pane(workspace, ¢er_pane, cx); cx.subscribe(&workspace_handle, { move |workspace, _, event, cx| { if let workspace::Event::PaneAdded(pane) = event { - pane.update(cx, |pane, cx| { - pane.toolbar().update(cx, |toolbar, 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); - - let quick_action_bar = cx - .build_view(|_| QuickActionBar::new(buffer_search_bar, workspace)); - toolbar.add_item(quick_action_bar, cx); - let diagnostic_editor_controls = - cx.build_view(|_| diagnostics::ToolbarControls::new()); - // toolbar.add_item(diagnostic_editor_controls, cx); - // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); - // toolbar.add_item(project_search_bar, cx); - // let submit_feedback_button = - // cx.add_view(|_| SubmitFeedbackButton::new()); - // toolbar.add_item(submit_feedback_button, cx); - // let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); - // toolbar.add_item(feedback_info_text, cx); - // let lsp_log_item = - // cx.add_view(|_| language_tools::LspLogToolbarItemView::new()); - // toolbar.add_item(lsp_log_item, cx); - // let syntax_tree_item = cx - // .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new()); - // toolbar.add_item(syntax_tree_item, cx); - }) - }); + initialize_pane(workspace, pane, cx); } } }) @@ -434,6 +410,36 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { .detach(); } +fn initialize_pane(workspace: &mut Workspace, pane: &View, cx: &mut ViewContext) { + pane.update(cx, |pane, cx| { + pane.toolbar().update(cx, |toolbar, 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); + + let quick_action_bar = + cx.build_view(|_| QuickActionBar::new(buffer_search_bar, workspace)); + toolbar.add_item(quick_action_bar, cx); + let diagnostic_editor_controls = cx.build_view(|_| diagnostics::ToolbarControls::new()); + // toolbar.add_item(diagnostic_editor_controls, cx); + // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); + // toolbar.add_item(project_search_bar, cx); + // let submit_feedback_button = + // cx.add_view(|_| SubmitFeedbackButton::new()); + // toolbar.add_item(submit_feedback_button, cx); + // let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); + // toolbar.add_item(feedback_info_text, cx); + // let lsp_log_item = + // cx.add_view(|_| language_tools::LspLogToolbarItemView::new()); + // toolbar.add_item(lsp_log_item, cx); + // let syntax_tree_item = cx + // .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new()); + // toolbar.add_item(syntax_tree_item, cx); + }) + }); +} + fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { use std::fmt::Write as _; From 5e558e2a58e6b1e38df3df6480a2efd2288f3ec0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 6 Dec 2023 17:57:18 +0100 Subject: [PATCH 21/23] Make more menu-related platform methods no-ops --- crates/gpui2/src/platform/test/platform.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index edbe39480994a9425f733ea5a5ed967f63535f11..876120b62626657541fabd1b790e857e16bd5865 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -214,17 +214,11 @@ impl Platform for TestPlatform { fn set_menus(&self, _menus: Vec, _keymap: &Keymap) {} - fn on_app_menu_action(&self, _callback: Box) { - unimplemented!() - } + fn on_app_menu_action(&self, _callback: Box) {} - fn on_will_open_app_menu(&self, _callback: Box) { - unimplemented!() - } + fn on_will_open_app_menu(&self, _callback: Box) {} - fn on_validate_app_menu_command(&self, _callback: Box bool>) { - unimplemented!() - } + fn on_validate_app_menu_command(&self, _callback: Box bool>) {} fn os_name(&self) -> &'static str { "test" From 2aee3e3192cd7fd682a288bc27444ef5954089c7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 6 Dec 2023 18:02:45 +0100 Subject: [PATCH 22/23] Make `Node::context` optional as well This was an oversight in d09dfe0. Co-Authored-By: Marshall --- crates/gpui2/src/key_dispatch.rs | 10 +++++----- crates/gpui2/src/window.rs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 80e662ad3ef6477b4cd0d925b2852957a4c04d00..7b8d506d03b2feb5c349838625a51e9d2d27b4b7 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -28,7 +28,7 @@ pub(crate) struct DispatchTree { pub(crate) struct DispatchNode { pub key_listeners: SmallVec<[KeyListener; 2]>, pub action_listeners: SmallVec<[DispatchActionListener; 16]>, - pub context: KeyContext, + pub context: Option, parent: Option, } @@ -70,14 +70,14 @@ impl DispatchTree { }); self.node_stack.push(node_id); if let Some(context) = context { - self.active_node().context = context.clone(); + self.active_node().context = Some(context.clone()); self.context_stack.push(context); } } pub fn pop_node(&mut self) { let node_id = self.node_stack.pop().unwrap(); - if !self.nodes[node_id.0].context.is_empty() { + if self.nodes[node_id.0].context.is_some() { self.context_stack.pop(); } } @@ -95,8 +95,8 @@ impl DispatchTree { self.context_stack.clear(); for node_id in dispatch_path { let node = self.node(node_id); - if !node.context.is_empty() { - self.context_stack.push(node.context.clone()); + if let Some(context) = node.context.clone() { + self.context_stack.push(context); } if let Some((context_stack, matcher)) = old_tree diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 8995d04b64de4abac4653e228691f3784eb89a6e..2f4708984355661c90764e1a00daf31084259486 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1393,8 +1393,8 @@ impl<'a> WindowContext<'a> { for node_id in &dispatch_path { let node = self.window.current_frame.dispatch_tree.node(*node_id); - if !node.context.is_empty() { - context_stack.push(node.context.clone()); + if let Some(context) = node.context.clone() { + context_stack.push(context); } for key_listener in node.key_listeners.clone() { @@ -1418,7 +1418,7 @@ impl<'a> WindowContext<'a> { // Match keystrokes let node = self.window.current_frame.dispatch_tree.node(*node_id); - if !node.context.is_empty() { + if node.context.is_some() { if let Some(key_down_event) = event.downcast_ref::() { if let Some(found) = self .window @@ -1563,7 +1563,7 @@ impl<'a> WindowContext<'a> { let context_stack = dispatch_tree .dispatch_path(node_id) .into_iter() - .map(|node_id| dispatch_tree.node(node_id).context.clone()) + .filter_map(|node_id| dispatch_tree.node(node_id).context.clone()) .collect(); dispatch_tree.bindings_for_action(action, &context_stack) } From f6a7a6c4d4df8ec1cfcc7fc299a494c1718c26d6 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 6 Dec 2023 12:03:59 -0500 Subject: [PATCH 23/23] v0.117.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 004214c9c85f0642e579c60672b40b04626ed1f8..0d4bb71f9826e4f35f096ef3661afaceb60f4fff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11706,7 +11706,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.116.0" +version = "0.117.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 245bb4cd5895e6f72641241b193987780bdec768..6d9cb3c7502bc539cedc26d45c97fde5e5b7099f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.116.0" +version = "0.117.0" publish = false [lib]