diff --git a/Cargo.lock b/Cargo.lock
index 1f7c9bc814..84abc6e101 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1356,6 +1356,23 @@ dependencies = [
"workspace",
]
+[[package]]
+name = "copilot_button"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "context_menu",
+ "copilot",
+ "editor",
+ "futures 0.3.25",
+ "gpui",
+ "settings",
+ "smol",
+ "theme",
+ "util",
+ "workspace",
+]
+
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -5924,6 +5941,7 @@ dependencies = [
"gpui",
"json_comments",
"postage",
+ "pretty_assertions",
"schemars",
"serde",
"serde_derive",
@@ -8507,6 +8525,7 @@ dependencies = [
"command_palette",
"context_menu",
"copilot",
+ "copilot_button",
"ctor",
"db",
"diagnostics",
diff --git a/Cargo.toml b/Cargo.toml
index f097b5b2c7..8fad52c8f4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,7 @@ members = [
"crates/command_palette",
"crates/context_menu",
"crates/copilot",
+ "crates/copilot_button",
"crates/db",
"crates/diagnostics",
"crates/drag_and_drop",
diff --git a/assets/icons/maybe_link_out.svg b/assets/icons/maybe_link_out.svg
new file mode 100644
index 0000000000..561f012452
--- /dev/null
+++ b/assets/icons/maybe_link_out.svg
@@ -0,0 +1,5 @@
+
diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs
index 3228f7d5a6..b5e8696ec7 100644
--- a/crates/collab_ui/src/collab_titlebar_item.rs
+++ b/crates/collab_ui/src/collab_titlebar_item.rs
@@ -301,25 +301,13 @@ impl CollabTitlebarItem {
.with_style(item_style.container)
.boxed()
})),
- ContextMenuItem::Item {
- label: "Sign out".into(),
- action: Box::new(SignOut),
- },
- ContextMenuItem::Item {
- label: "Send Feedback".into(),
- action: Box::new(feedback::feedback_editor::GiveFeedback),
- },
+ ContextMenuItem::item("Sign out", SignOut),
+ ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback),
]
} else {
vec![
- ContextMenuItem::Item {
- label: "Sign in".into(),
- action: Box::new(SignIn),
- },
- ContextMenuItem::Item {
- label: "Send Feedback".into(),
- action: Box::new(feedback::feedback_editor::GiveFeedback),
- },
+ ContextMenuItem::item("Sign in", SignIn),
+ ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback),
]
};
diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs
index e1b9f81c1a..ffc121576e 100644
--- a/crates/context_menu/src/context_menu.rs
+++ b/crates/context_menu/src/context_menu.rs
@@ -1,7 +1,7 @@
use gpui::{
elements::*, geometry::vector::Vector2F, impl_internal_actions, keymap_matcher::KeymapContext,
platform::CursorStyle, Action, AnyViewHandle, AppContext, Axis, Entity, MouseButton,
- MutableAppContext, RenderContext, SizeConstraint, Subscription, View, ViewContext,
+ MouseState, MutableAppContext, RenderContext, SizeConstraint, Subscription, View, ViewContext,
};
use menu::*;
use settings::Settings;
@@ -24,20 +24,71 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ContextMenu::cancel);
}
+type ContextMenuItemBuilder = Box ElementBox>;
+
+pub enum ContextMenuItemLabel {
+ String(Cow<'static, str>),
+ Element(ContextMenuItemBuilder),
+}
+
+pub enum ContextMenuAction {
+ ParentAction {
+ action: Box,
+ },
+ ViewAction {
+ action: Box,
+ for_view: usize,
+ },
+}
+
+impl ContextMenuAction {
+ fn id(&self) -> TypeId {
+ match self {
+ ContextMenuAction::ParentAction { action } => action.id(),
+ ContextMenuAction::ViewAction { action, .. } => action.id(),
+ }
+ }
+}
+
pub enum ContextMenuItem {
Item {
- label: Cow<'static, str>,
- action: Box,
+ label: ContextMenuItemLabel,
+ action: ContextMenuAction,
},
Static(StaticItem),
Separator,
}
impl ContextMenuItem {
+ pub fn element_item(label: ContextMenuItemBuilder, action: impl 'static + Action) -> Self {
+ Self::Item {
+ label: ContextMenuItemLabel::Element(label),
+ action: ContextMenuAction::ParentAction {
+ action: Box::new(action),
+ },
+ }
+ }
+
pub fn item(label: impl Into>, action: impl 'static + Action) -> Self {
Self::Item {
- label: label.into(),
- action: Box::new(action),
+ label: ContextMenuItemLabel::String(label.into()),
+ action: ContextMenuAction::ParentAction {
+ action: Box::new(action),
+ },
+ }
+ }
+
+ pub fn item_for_view(
+ label: impl Into>,
+ view_id: usize,
+ action: impl 'static + Action,
+ ) -> Self {
+ Self::Item {
+ label: ContextMenuItemLabel::String(label.into()),
+ action: ContextMenuAction::ViewAction {
+ action: Box::new(action),
+ for_view: view_id,
+ },
}
}
@@ -168,7 +219,15 @@ impl ContextMenu {
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) {
if let Some(ix) = self.selected_index {
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
- cx.dispatch_any_action(action.boxed_clone());
+ match action {
+ ContextMenuAction::ParentAction { action } => {
+ cx.dispatch_any_action(action.boxed_clone())
+ }
+ ContextMenuAction::ViewAction { action, for_view } => {
+ let window_id = cx.window_id();
+ cx.dispatch_any_action_at(window_id, *for_view, action.boxed_clone())
+ }
+ };
self.reset(cx);
}
}
@@ -278,10 +337,17 @@ impl ContextMenu {
Some(ix) == self.selected_index,
);
- Label::new(label.to_string(), style.label.clone())
- .contained()
- .with_style(style.container)
- .boxed()
+ match label {
+ ContextMenuItemLabel::String(label) => {
+ Label::new(label.to_string(), style.label.clone())
+ .contained()
+ .with_style(style.container)
+ .boxed()
+ }
+ ContextMenuItemLabel::Element(element) => {
+ element(&mut Default::default(), style)
+ }
+ }
}
ContextMenuItem::Static(f) => f(cx),
@@ -306,9 +372,18 @@ impl ContextMenu {
&mut Default::default(),
Some(ix) == self.selected_index,
);
+ let (action, view_id) = match action {
+ ContextMenuAction::ParentAction { action } => {
+ (action.boxed_clone(), self.parent_view_id)
+ }
+ ContextMenuAction::ViewAction { action, for_view } => {
+ (action.boxed_clone(), *for_view)
+ }
+ };
+
KeystrokeLabel::new(
window_id,
- self.parent_view_id,
+ view_id,
action.boxed_clone(),
style.keystroke.container,
style.keystroke.text.clone(),
@@ -347,22 +422,34 @@ impl ContextMenu {
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item {
ContextMenuItem::Item { label, action } => {
- let action = action.boxed_clone();
+ let (action, view_id) = match action {
+ ContextMenuAction::ParentAction { action } => {
+ (action.boxed_clone(), self.parent_view_id)
+ }
+ ContextMenuAction::ViewAction { action, for_view } => {
+ (action.boxed_clone(), *for_view)
+ }
+ };
MouseEventHandler::