Merge branch 'main' into copilot2

This commit is contained in:
Piotr Osiewicz
2023-12-06 15:13:13 +01:00
100 changed files with 10636 additions and 3033 deletions
+61 -23
View File
@@ -7,7 +7,7 @@ use gpui::{
IntoElement, Render, View, VisualContext,
};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
use std::rc::Rc;
use std::{rc::Rc, time::Duration};
pub enum ContextMenuItem {
Separator,
@@ -16,7 +16,7 @@ pub enum ContextMenuItem {
label: SharedString,
icon: Option<Icon>,
handler: Rc<dyn Fn(&mut WindowContext)>,
key_binding: Option<KeyBinding>,
action: Option<Box<dyn Action>>,
},
}
@@ -24,6 +24,7 @@ pub struct ContextMenu {
items: Vec<ContextMenuItem>,
focus_handle: FocusHandle,
selected_index: Option<usize>,
delayed: bool,
}
impl FocusableView for ContextMenu {
@@ -46,6 +47,7 @@ impl ContextMenu {
items: Default::default(),
focus_handle: cx.focus_handle(),
selected_index: None,
delayed: false,
},
cx,
)
@@ -70,36 +72,26 @@ impl ContextMenu {
self.items.push(ContextMenuItem::Entry {
label: label.into(),
handler: Rc::new(on_click),
key_binding: None,
icon: None,
action: None,
});
self
}
pub fn action(
mut self,
label: impl Into<SharedString>,
action: Box<dyn Action>,
cx: &mut WindowContext,
) -> Self {
pub fn action(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
self.items.push(ContextMenuItem::Entry {
label: label.into(),
key_binding: KeyBinding::for_action(&*action, cx),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
icon: None,
});
self
}
pub fn link(
mut self,
label: impl Into<SharedString>,
action: Box<dyn Action>,
cx: &mut WindowContext,
) -> Self {
pub fn link(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
self.items.push(ContextMenuItem::Entry {
label: label.into(),
key_binding: KeyBinding::for_action(&*action, cx),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
icon: Some(Icon::Link),
});
@@ -161,6 +153,37 @@ impl ContextMenu {
self.select_last(&Default::default(), cx);
}
}
pub fn on_action_dispatch(&mut self, dispatched: &Box<dyn Action>, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.items.iter().position(|item| {
if let ContextMenuItem::Entry {
action: Some(action),
..
} = item
{
action.partial_eq(&**dispatched)
} else {
false
}
}) {
self.selected_index = Some(ix);
self.delayed = true;
cx.notify();
let action = dispatched.boxed_clone();
cx.spawn(|this, mut cx| async move {
cx.background_executor()
.timer(Duration::from_millis(50))
.await;
this.update(&mut cx, |this, cx| {
cx.dispatch_action(action);
this.cancel(&Default::default(), cx)
})
})
.detach_and_log_err(cx);
} else {
cx.propagate()
}
}
}
impl ContextMenuItem {
@@ -185,6 +208,22 @@ impl Render for ContextMenu {
.on_action(cx.listener(ContextMenu::select_prev))
.on_action(cx.listener(ContextMenu::confirm))
.on_action(cx.listener(ContextMenu::cancel))
.when(!self.delayed, |mut el| {
for item in self.items.iter() {
if let ContextMenuItem::Entry {
action: Some(action),
..
} = item
{
el = el.on_boxed_action(
action,
cx.listener(ContextMenu::on_action_dispatch),
);
}
}
el
})
.on_blur(cx.listener(|this, _, cx| this.cancel(&Default::default(), cx)))
.flex_none()
.child(
List::new().children(self.items.iter().enumerate().map(
@@ -196,8 +235,8 @@ impl Render for ContextMenu {
ContextMenuItem::Entry {
label,
handler,
key_binding,
icon,
action,
} => {
let handler = handler.clone();
let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent));
@@ -218,11 +257,10 @@ impl Render for ContextMenu {
.w_full()
.justify_between()
.child(label_element)
.children(
key_binding
.clone()
.map(|binding| div().ml_1().child(binding)),
),
.children(action.as_ref().and_then(|action| {
KeyBinding::for_action(&**action, cx)
.map(|binding| div().ml_1().child(binding))
})),
)
.selected(Some(ix) == self.selected_index)
.on_click(move |event, cx| {
+13 -7
View File
@@ -1,15 +1,26 @@
use gpui::{rems, svg, IntoElement, Svg};
use gpui::{rems, svg, IntoElement, Rems, Svg};
use strum::EnumIter;
use crate::prelude::*;
#[derive(Default, PartialEq, Copy, Clone)]
pub enum IconSize {
XSmall,
Small,
#[default]
Medium,
}
impl IconSize {
pub fn rems(self) -> Rems {
match self {
IconSize::XSmall => rems(12. / 16.),
IconSize::Small => rems(14. / 16.),
IconSize::Medium => rems(16. / 16.),
}
}
}
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
pub enum Icon {
Ai,
@@ -173,13 +184,8 @@ impl RenderOnce for IconElement {
type Rendered = Svg;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let svg_size = match self.size {
IconSize::Small => rems(14. / 16.),
IconSize::Medium => rems(16. / 16.),
};
svg()
.size(svg_size)
.size(self.size.rems())
.flex_none()
.path(self.path)
.text_color(self.color.color(cx))
+60
View File
@@ -0,0 +1,60 @@
use gpui::{Div, Position};
use crate::prelude::*;
#[derive(Default)]
pub enum IndicatorStyle {
#[default]
Dot,
Bar,
}
#[derive(IntoElement)]
pub struct Indicator {
position: Position,
style: IndicatorStyle,
color: Color,
}
impl Indicator {
pub fn dot() -> Self {
Self {
position: Position::Relative,
style: IndicatorStyle::Dot,
color: Color::Default,
}
}
pub fn bar() -> Self {
Self {
position: Position::Relative,
style: IndicatorStyle::Dot,
color: Color::Default,
}
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn absolute(mut self) -> Self {
self.position = Position::Absolute;
self
}
}
impl RenderOnce for Indicator {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div()
.flex_none()
.map(|this| match self.style {
IndicatorStyle::Dot => this.w_1p5().h_1p5().rounded_full(),
IndicatorStyle::Bar => this.w_full().h_1p5().rounded_t_md(),
})
.when(self.position == Position::Absolute, |this| this.absolute())
.bg(self.color.color(cx))
}
}
+12 -3
View File
@@ -1,5 +1,5 @@
use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
use gpui::{relative, rems, Action, Div, IntoElement, Keystroke};
use gpui::{relative, rems, Action, Div, FocusHandle, IntoElement, Keystroke};
#[derive(IntoElement, Clone)]
pub struct KeyBinding {
@@ -49,12 +49,21 @@ impl RenderOnce for KeyBinding {
impl KeyBinding {
pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
// todo! this last is arbitrary, we want to prefer users key bindings over defaults,
// and vim over normal (in vim mode), etc.
let key_binding = cx.bindings_for_action(action).last().cloned()?;
Some(Self::new(key_binding))
}
// like for_action(), but lets you specify the context from which keybindings
// are matched.
pub fn for_action_in(
action: &dyn Action,
focus: &FocusHandle,
cx: &mut WindowContext,
) -> Option<Self> {
let key_binding = cx.bindings_for_action_in(action, focus).last().cloned()?;
Some(Self::new(key_binding))
}
fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
let mut icon: Option<Icon> = None;
+10 -1
View File
@@ -1,7 +1,8 @@
use std::rc::Rc;
use gpui::{
px, AnyElement, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels, Stateful,
px, AnyElement, AnyView, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels,
Stateful,
};
use smallvec::SmallVec;
@@ -21,6 +22,7 @@ pub struct ListItem {
inset: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
children: SmallVec<[AnyElement; 2]>,
}
@@ -38,6 +40,7 @@ impl ListItem {
on_click: None,
on_secondary_mouse_down: None,
on_toggle: None,
tooltip: None,
children: SmallVec::new(),
}
}
@@ -55,6 +58,11 @@ impl ListItem {
self
}
pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
self.tooltip = Some(Box::new(tooltip));
self
}
pub fn inset(mut self, inset: bool) -> Self {
self.inset = inset;
self
@@ -149,6 +157,7 @@ impl RenderOnce for ListItem {
(on_mouse_down)(event, cx)
})
})
.when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
.child(
div()
.when(self.inset, |this| this.px_2())