) -> Self::Element {
div()
.key_context("ConversationEditor")
- .size_full()
- .relative()
.capture_action(cx.listener(ConversationEditor::cancel_last_assist))
.capture_action(cx.listener(ConversationEditor::save))
.capture_action(cx.listener(ConversationEditor::copy))
.capture_action(cx.listener(ConversationEditor::cycle_message_role))
.on_action(cx.listener(ConversationEditor::assist))
.on_action(cx.listener(ConversationEditor::split))
- .child(self.editor.clone())
+ .size_full()
+ .relative()
+ .child(
+ div()
+ .size_full()
+ .pl_2()
+ .bg(cx.theme().colors().editor_background)
+ .child(self.editor.clone()),
+ )
.child(
h_stack()
.absolute()
diff --git a/crates/ui2/src/components/divider.rs b/crates/ui2/src/components/divider.rs
index cb48ce00ae24f031807d8522510aa18bc586d638..20744d6c48e94ea80312acd9bf3ef63b7a30496f 100644
--- a/crates/ui2/src/components/divider.rs
+++ b/crates/ui2/src/components/divider.rs
@@ -1,4 +1,4 @@
-use gpui::{Div, IntoElement};
+use gpui::{Div, Hsla, IntoElement};
use crate::prelude::*;
@@ -7,9 +7,26 @@ enum DividerDirection {
Vertical,
}
+#[derive(Default)]
+pub enum DividerColor {
+ Border,
+ #[default]
+ BorderVariant,
+}
+
+impl DividerColor {
+ pub fn hsla(self, cx: &WindowContext) -> Hsla {
+ match self {
+ DividerColor::Border => cx.theme().colors().border,
+ DividerColor::BorderVariant => cx.theme().colors().border_variant,
+ }
+ }
+}
+
#[derive(IntoElement)]
pub struct Divider {
direction: DividerDirection,
+ color: DividerColor,
inset: bool,
}
@@ -26,7 +43,7 @@ impl RenderOnce for Divider {
this.w_px().h_full().when(self.inset, |this| this.my_1p5())
}
})
- .bg(cx.theme().colors().border_variant)
+ .bg(self.color.hsla(cx))
}
}
@@ -34,6 +51,7 @@ impl Divider {
pub fn horizontal() -> Self {
Self {
direction: DividerDirection::Horizontal,
+ color: DividerColor::default(),
inset: false,
}
}
@@ -41,6 +59,7 @@ impl Divider {
pub fn vertical() -> Self {
Self {
direction: DividerDirection::Vertical,
+ color: DividerColor::default(),
inset: false,
}
}
@@ -49,4 +68,9 @@ impl Divider {
self.inset = true;
self
}
+
+ pub fn color(mut self, color: DividerColor) -> Self {
+ self.color = color;
+ self
+ }
}
diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs
index 6216acac483489f283366177fe5b6416495122c9..ca50cae7f8936e50dbc66be2def222589a571207 100644
--- a/crates/ui2/src/components/icon.rs
+++ b/crates/ui2/src/components/icon.rs
@@ -75,6 +75,7 @@ pub enum Icon {
MagnifyingGlass,
MailOpen,
Maximize,
+ Minimize,
Menu,
MessageBubbles,
Mic,
@@ -88,7 +89,7 @@ pub enum Icon {
Screen,
SelectAll,
Split,
- SplitMessage,
+ Snip,
Terminal,
WholeWord,
XCircle,
@@ -156,6 +157,7 @@ impl Icon {
Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
Icon::MailOpen => "icons/mail-open.svg",
Icon::Maximize => "icons/maximize.svg",
+ Icon::Minimize => "icons/minimize.svg",
Icon::Menu => "icons/menu.svg",
Icon::MessageBubbles => "icons/conversations.svg",
Icon::Mic => "icons/mic.svg",
@@ -169,7 +171,7 @@ impl Icon {
Icon::Screen => "icons/desktop.svg",
Icon::SelectAll => "icons/select-all.svg",
Icon::Split => "icons/split.svg",
- Icon::SplitMessage => "icons/split_message.svg",
+ Icon::Snip => "icons/snip.svg",
Icon::Terminal => "icons/terminal.svg",
Icon::WholeWord => "icons/word_search.svg",
Icon::XCircle => "icons/error.svg",
diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs
index 8114a322e300cac465981438bf8ad02d84ca029e..eddfab811914b721126ccf777c9818498e405569 100644
--- a/crates/ui2/src/components/tab.rs
+++ b/crates/ui2/src/components/tab.rs
@@ -48,6 +48,8 @@ impl Tab {
}
}
+ pub const HEIGHT_IN_REMS: f32 = 30. / 16.;
+
pub fn position(mut self, position: TabPosition) -> Self {
self.position = position;
self
@@ -94,8 +96,6 @@ impl RenderOnce for Tab {
type Rendered = Stateful;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
- const HEIGHT_IN_REMS: f32 = 30. / 16.;
-
let (text_color, tab_bg, _tab_hover_bg, _tab_active_bg) = match self.selected {
false => (
cx.theme().colors().text_muted,
@@ -112,7 +112,7 @@ impl RenderOnce for Tab {
};
self.div
- .h(rems(HEIGHT_IN_REMS))
+ .h(rems(Self::HEIGHT_IN_REMS))
.bg(tab_bg)
.border_color(cx.theme().colors().border)
.map(|this| match self.position {
diff --git a/crates/ui2/src/utils/format_distance.rs b/crates/ui2/src/utils/format_distance.rs
index d157d30bbebfeb28103c9aea3cc6193253f81e93..b0fce184ce64e23e1173ddcce77f3532fb0dfe5e 100644
--- a/crates/ui2/src/utils/format_distance.rs
+++ b/crates/ui2/src/utils/format_distance.rs
@@ -1,4 +1,72 @@
-use chrono::NaiveDateTime;
+use chrono::{DateTime, Local, NaiveDateTime};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum DateTimeType {
+ Naive(NaiveDateTime),
+ Local(DateTime),
+}
+
+impl DateTimeType {
+ /// Converts the DateTimeType to a NaiveDateTime.
+ ///
+ /// If the DateTimeType is already a NaiveDateTime, it will be returned as is.
+ /// If the DateTimeType is a DateTime, it will be converted to a NaiveDateTime.
+ pub fn to_naive(&self) -> NaiveDateTime {
+ match self {
+ DateTimeType::Naive(naive) => *naive,
+ DateTimeType::Local(local) => local.naive_local(),
+ }
+ }
+}
+
+pub struct FormatDistance {
+ date: DateTimeType,
+ base_date: DateTimeType,
+ include_seconds: bool,
+ add_suffix: bool,
+ hide_prefix: bool,
+}
+
+impl FormatDistance {
+ pub fn new(date: DateTimeType, base_date: DateTimeType) -> Self {
+ Self {
+ date,
+ base_date,
+ include_seconds: false,
+ add_suffix: false,
+ hide_prefix: false,
+ }
+ }
+
+ pub fn from_now(date: DateTimeType) -> Self {
+ Self::new(date, DateTimeType::Local(Local::now()))
+ }
+
+ pub fn to_string(self) -> String {
+ format_distance(
+ self.date,
+ self.base_date.to_naive(),
+ self.include_seconds,
+ self.add_suffix,
+ self.hide_prefix,
+ )
+ }
+
+ pub fn include_seconds(mut self, include_seconds: bool) -> Self {
+ self.include_seconds = include_seconds;
+ self
+ }
+
+ pub fn add_suffix(mut self, add_suffix: bool) -> Self {
+ self.add_suffix = add_suffix;
+ self
+ }
+
+ pub fn hide_prefix(mut self, hide_prefix: bool) -> Self {
+ self.hide_prefix = hide_prefix;
+ self
+ }
+}
/// Calculates the distance in seconds between two NaiveDateTime objects.
/// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative.
@@ -13,7 +81,12 @@ fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
}
/// Generates a string describing the time distance between two dates in a human-readable way.
-fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
+fn distance_string(
+ distance: i64,
+ include_seconds: bool,
+ add_suffix: bool,
+ hide_prefix: bool,
+) -> String {
let suffix = if distance < 0 { " from now" } else { " ago" };
let distance = distance.abs();
@@ -24,53 +97,128 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St
let months = distance / 2_592_000;
let string = if distance < 5 && include_seconds {
- "less than 5 seconds".to_string()
+ if hide_prefix {
+ "5 seconds"
+ } else {
+ "less than 5 seconds"
+ }
+ .to_string()
} else if distance < 10 && include_seconds {
- "less than 10 seconds".to_string()
+ if hide_prefix {
+ "10 seconds"
+ } else {
+ "less than 10 seconds"
+ }
+ .to_string()
} else if distance < 20 && include_seconds {
- "less than 20 seconds".to_string()
+ if hide_prefix {
+ "20 seconds"
+ } else {
+ "less than 20 seconds"
+ }
+ .to_string()
} else if distance < 40 && include_seconds {
- "half a minute".to_string()
+ if hide_prefix {
+ "half a minute"
+ } else {
+ "half a minute"
+ }
+ .to_string()
} else if distance < 60 && include_seconds {
- "less than a minute".to_string()
+ if hide_prefix {
+ "a minute"
+ } else {
+ "less than a minute"
+ }
+ .to_string()
} else if distance < 90 && include_seconds {
"1 minute".to_string()
} else if distance < 30 {
- "less than a minute".to_string()
+ if hide_prefix {
+ "a minute"
+ } else {
+ "less than a minute"
+ }
+ .to_string()
} else if distance < 90 {
"1 minute".to_string()
} else if distance < 2_700 {
format!("{} minutes", minutes)
} else if distance < 5_400 {
- "about 1 hour".to_string()
+ if hide_prefix {
+ "1 hour"
+ } else {
+ "about 1 hour"
+ }
+ .to_string()
} else if distance < 86_400 {
- format!("about {} hours", hours)
+ if hide_prefix {
+ format!("{} hours", hours)
+ } else {
+ format!("about {} hours", hours)
+ }
+ .to_string()
} else if distance < 172_800 {
"1 day".to_string()
} else if distance < 2_592_000 {
format!("{} days", days)
} else if distance < 5_184_000 {
- "about 1 month".to_string()
+ if hide_prefix {
+ "1 month"
+ } else {
+ "about 1 month"
+ }
+ .to_string()
} else if distance < 7_776_000 {
- "about 2 months".to_string()
+ if hide_prefix {
+ "2 months"
+ } else {
+ "about 2 months"
+ }
+ .to_string()
} else if distance < 31_540_000 {
format!("{} months", months)
} else if distance < 39_425_000 {
- "about 1 year".to_string()
+ if hide_prefix {
+ "1 year"
+ } else {
+ "about 1 year"
+ }
+ .to_string()
} else if distance < 55_195_000 {
- "over 1 year".to_string()
+ if hide_prefix { "1 year" } else { "over 1 year" }.to_string()
} else if distance < 63_080_000 {
- "almost 2 years".to_string()
+ if hide_prefix {
+ "2 years"
+ } else {
+ "almost 2 years"
+ }
+ .to_string()
} else {
let years = distance / 31_536_000;
let remaining_months = (distance % 31_536_000) / 2_592_000;
if remaining_months < 3 {
- format!("about {} years", years)
+ if hide_prefix {
+ format!("{} years", years)
+ } else {
+ format!("about {} years", years)
+ }
+ .to_string()
} else if remaining_months < 9 {
- format!("over {} years", years)
+ if hide_prefix {
+ format!("{} years", years)
+ } else {
+ format!("over {} years", years)
+ }
+ .to_string()
} else {
- format!("almost {} years", years + 1)
+ if hide_prefix {
+ format!("{} years", years + 1)
+ } else {
+ format!("almost {} years", years + 1)
+ }
+ .to_string()
}
};
@@ -108,15 +256,16 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St
/// ```
///
/// Output: `"There was about 3 years between the first and last crewed moon landings."`
-pub fn naive_format_distance(
- date: NaiveDateTime,
+pub fn format_distance(
+ date: DateTimeType,
base_date: NaiveDateTime,
include_seconds: bool,
add_suffix: bool,
+ hide_prefix: bool,
) -> String {
- let distance = distance_in_seconds(date, base_date);
+ let distance = distance_in_seconds(date.to_naive(), base_date);
- distance_string(distance, include_seconds, add_suffix)
+ distance_string(distance, include_seconds, add_suffix, hide_prefix)
}
/// Get the time difference between a date and now as relative human readable string.
@@ -142,14 +291,15 @@ pub fn naive_format_distance(
/// ```
///
/// Output: `It's been over 54 years since Apollo 11 first landed on the moon.`
-pub fn naive_format_distance_from_now(
- datetime: NaiveDateTime,
+pub fn format_distance_from_now(
+ datetime: DateTimeType,
include_seconds: bool,
add_suffix: bool,
+ hide_prefix: bool,
) -> String {
let now = chrono::offset::Local::now().naive_local();
- naive_format_distance(datetime, now, include_seconds, add_suffix)
+ format_distance(datetime, now, include_seconds, add_suffix, hide_prefix)
}
#[cfg(test)]
@@ -158,73 +308,127 @@ mod tests {
use chrono::NaiveDateTime;
#[test]
- fn test_naive_format_distance() {
- let date =
- NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
- let base_date =
- NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
+ fn test_format_distance() {
+ let date = DateTimeType::Naive(
+ NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"),
+ );
+ let base_date = DateTimeType::Naive(
+ NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"),
+ );
assert_eq!(
"about 2 hours",
- naive_format_distance(date, base_date, false, false)
+ format_distance(date, base_date.to_naive(), false, false, false)
);
}
#[test]
- fn test_naive_format_distance_with_suffix() {
- let date =
- NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
- let base_date =
- NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
+ fn test_format_distance_with_suffix() {
+ let date = DateTimeType::Naive(
+ NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date"),
+ );
+ let base_date = DateTimeType::Naive(
+ NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date"),
+ );
assert_eq!(
"about 2 hours from now",
- naive_format_distance(date, base_date, false, true)
+ format_distance(date, base_date.to_naive(), false, true, false)
);
}
#[test]
- fn test_naive_format_distance_from_now() {
- let date = NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ")
- .expect("Invalid NaiveDateTime for date");
+ fn test_format_distance_from_now() {
+ let date = DateTimeType::Naive(
+ NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ")
+ .expect("Invalid NaiveDateTime for date"),
+ );
assert_eq!(
"over 54 years ago",
- naive_format_distance_from_now(date, false, true)
+ format_distance_from_now(date, false, true, false)
);
}
#[test]
- fn test_naive_format_distance_string() {
- assert_eq!(distance_string(3, false, false), "less than a minute");
- assert_eq!(distance_string(7, false, false), "less than a minute");
- assert_eq!(distance_string(13, false, false), "less than a minute");
- assert_eq!(distance_string(21, false, false), "less than a minute");
- assert_eq!(distance_string(45, false, false), "1 minute");
- assert_eq!(distance_string(61, false, false), "1 minute");
- assert_eq!(distance_string(1920, false, false), "32 minutes");
- assert_eq!(distance_string(3902, false, false), "about 1 hour");
- assert_eq!(distance_string(18002, false, false), "about 5 hours");
- assert_eq!(distance_string(86470, false, false), "1 day");
- assert_eq!(distance_string(345880, false, false), "4 days");
- assert_eq!(distance_string(2764800, false, false), "about 1 month");
- assert_eq!(distance_string(5184000, false, false), "about 2 months");
- assert_eq!(distance_string(10368000, false, false), "4 months");
- assert_eq!(distance_string(34694000, false, false), "about 1 year");
- assert_eq!(distance_string(47310000, false, false), "over 1 year");
- assert_eq!(distance_string(61503000, false, false), "almost 2 years");
- assert_eq!(distance_string(160854000, false, false), "about 5 years");
- assert_eq!(distance_string(236550000, false, false), "over 7 years");
- assert_eq!(distance_string(249166000, false, false), "almost 8 years");
+ fn test_format_distance_string() {
+ assert_eq!(
+ distance_string(3, false, false, false),
+ "less than a minute"
+ );
+ assert_eq!(
+ distance_string(7, false, false, false),
+ "less than a minute"
+ );
+ assert_eq!(
+ distance_string(13, false, false, false),
+ "less than a minute"
+ );
+ assert_eq!(
+ distance_string(21, false, false, false),
+ "less than a minute"
+ );
+ assert_eq!(distance_string(45, false, false, false), "1 minute");
+ assert_eq!(distance_string(61, false, false, false), "1 minute");
+ assert_eq!(distance_string(1920, false, false, false), "32 minutes");
+ assert_eq!(distance_string(3902, false, false, false), "about 1 hour");
+ assert_eq!(distance_string(18002, false, false, false), "about 5 hours");
+ assert_eq!(distance_string(86470, false, false, false), "1 day");
+ assert_eq!(distance_string(345880, false, false, false), "4 days");
+ assert_eq!(
+ distance_string(2764800, false, false, false),
+ "about 1 month"
+ );
+ assert_eq!(
+ distance_string(5184000, false, false, false),
+ "about 2 months"
+ );
+ assert_eq!(distance_string(10368000, false, false, false), "4 months");
+ assert_eq!(
+ distance_string(34694000, false, false, false),
+ "about 1 year"
+ );
+ assert_eq!(
+ distance_string(47310000, false, false, false),
+ "over 1 year"
+ );
+ assert_eq!(
+ distance_string(61503000, false, false, false),
+ "almost 2 years"
+ );
+ assert_eq!(
+ distance_string(160854000, false, false, false),
+ "about 5 years"
+ );
+ assert_eq!(
+ distance_string(236550000, false, false, false),
+ "over 7 years"
+ );
+ assert_eq!(
+ distance_string(249166000, false, false, false),
+ "almost 8 years"
+ );
}
#[test]
- fn test_naive_format_distance_string_include_seconds() {
- assert_eq!(distance_string(3, true, false), "less than 5 seconds");
- assert_eq!(distance_string(7, true, false), "less than 10 seconds");
- assert_eq!(distance_string(13, true, false), "less than 20 seconds");
- assert_eq!(distance_string(21, true, false), "half a minute");
- assert_eq!(distance_string(45, true, false), "less than a minute");
- assert_eq!(distance_string(61, true, false), "1 minute");
+ fn test_format_distance_string_include_seconds() {
+ assert_eq!(
+ distance_string(3, true, false, false),
+ "less than 5 seconds"
+ );
+ assert_eq!(
+ distance_string(7, true, false, false),
+ "less than 10 seconds"
+ );
+ assert_eq!(
+ distance_string(13, true, false, false),
+ "less than 20 seconds"
+ );
+ assert_eq!(distance_string(21, true, false, false), "half a minute");
+ assert_eq!(
+ distance_string(45, true, false, false),
+ "less than a minute"
+ );
+ assert_eq!(distance_string(61, true, false, false), "1 minute");
}
}