diff --git a/assets/icons/copy.svg b/assets/icons/copy.svg
new file mode 100644
index 0000000000..8b755e8063
--- /dev/null
+++ b/assets/icons/copy.svg
@@ -0,0 +1 @@
+
diff --git a/crates/diagnostics2/src/diagnostics.rs b/crates/diagnostics2/src/diagnostics.rs
index 0a0f4da893..dd01f90b9f 100644
--- a/crates/diagnostics2/src/diagnostics.rs
+++ b/crates/diagnostics2/src/diagnostics.rs
@@ -774,24 +774,39 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
Arc::new(move |_| {
h_stack()
.id("diagnostic header")
- .gap_3()
- .bg(gpui::red())
- .map(|stack| {
- let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
- IconElement::new(Icon::XCircle).color(Color::Error)
- } else {
- IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)
- };
-
- stack.child(div().pl_8().child(icon))
- })
- .when_some(diagnostic.source.as_ref(), |stack, source| {
- stack.child(Label::new(format!("{source}:")).color(Color::Accent))
- })
- .child(HighlightedLabel::new(message.clone(), highlights.clone()))
- .when_some(diagnostic.code.as_ref(), |stack, code| {
- stack.child(Label::new(code.clone()))
- })
+ .py_2()
+ .pl_10()
+ .pr_5()
+ .w_full()
+ .justify_between()
+ .gap_2()
+ .child(
+ h_stack()
+ .gap_3()
+ .map(|stack| {
+ let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
+ IconElement::new(Icon::XCircle).color(Color::Error)
+ } else {
+ IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)
+ };
+ stack.child(icon)
+ })
+ .child(
+ h_stack()
+ .gap_1()
+ .child(HighlightedLabel::new(message.clone(), highlights.clone()))
+ .when_some(diagnostic.code.as_ref(), |stack, code| {
+ stack.child(Label::new(format!("({code})")).color(Color::Muted))
+ }),
+ ),
+ )
+ .child(
+ h_stack()
+ .gap_1()
+ .when_some(diagnostic.source.as_ref(), |stack, source| {
+ stack.child(Label::new(format!("{source}")).color(Color::Muted))
+ }),
+ )
.into_any_element()
})
}
@@ -802,11 +817,22 @@ pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElement {
label.into_any_element()
} else {
h_stack()
- .bg(gpui::red())
- .child(IconElement::new(Icon::XCircle))
- .child(Label::new(summary.error_count.to_string()))
- .child(IconElement::new(Icon::ExclamationTriangle))
- .child(Label::new(summary.warning_count.to_string()))
+ .gap_1()
+ .when(summary.error_count > 0, |then| {
+ then.child(
+ h_stack()
+ .gap_1()
+ .child(IconElement::new(Icon::XCircle).color(Color::Error))
+ .child(Label::new(summary.error_count.to_string())),
+ )
+ })
+ .when(summary.warning_count > 0, |then| {
+ then.child(
+ h_stack()
+ .child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning))
+ .child(Label::new(summary.warning_count.to_string())),
+ )
+ })
.into_any_element()
}
}
diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs
index 92fa4ca792..e9979a3ae2 100644
--- a/crates/editor2/src/editor.rs
+++ b/crates/editor2/src/editor.rs
@@ -100,8 +100,10 @@ use text::{OffsetUtf16, Rope};
use theme::{
ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
};
-use ui::prelude::*;
-use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, Tooltip};
+use ui::{
+ h_stack, v_stack, ButtonSize, ButtonStyle, HighlightedLabel, Icon, IconButton, Popover, Tooltip,
+};
+use ui::{prelude::*, IconSize};
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::{
item::{ItemEvent, ItemHandle},
@@ -9689,20 +9691,44 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
let message = diagnostic.message;
Arc::new(move |cx: &mut BlockContext| {
let message = message.clone();
+ let copy_id: SharedString = format!("copy-{}", cx.block_id.clone()).to_string().into();
+ // TODO: `cx.write_to_clipboard` is not implemented in tests.
+ // let write_to_clipboard = cx.write_to_clipboard(ClipboardItem::new(message.clone()));
+
+ // TODO: Nate: We should tint the background of the block with the severity color
+ // We need to extend the theme before we can do this
v_stack()
.id(cx.block_id)
+ .relative()
.size_full()
.bg(gpui::red())
.children(highlighted_lines.iter().map(|(line, highlights)| {
- div()
+ let group_id = cx.block_id.to_string();
+
+ h_stack()
+ .group(group_id.clone())
+ .gap_2()
+ .absolute()
+ .left(cx.anchor_x)
+ .px_1p5()
.child(HighlightedLabel::new(line.clone(), highlights.clone()))
- .ml(cx.anchor_x)
+ .child(
+ div()
+ .border()
+ .border_color(gpui::red())
+ .invisible()
+ .group_hover(group_id, |style| style.visible())
+ .child(
+ IconButton::new(copy_id.clone(), Icon::Copy)
+ .icon_color(Color::Muted)
+ .size(ButtonSize::Compact)
+ .style(ButtonStyle::Transparent)
+ // TODO: `cx.write_to_clipboard` is not implemented in tests.
+ // .on_click(cx.listener(move |_, _, cx| write_to_clipboard))
+ .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
+ ),
+ )
}))
- .cursor_pointer()
- .on_click(cx.listener(move |_, _, cx| {
- cx.write_to_clipboard(ClipboardItem::new(message.clone()));
- }))
- .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx))
.into_any_element()
})
}
diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs
index 3abe5a37f9..824b8c7df8 100644
--- a/crates/editor2/src/element.rs
+++ b/crates/editor2/src/element.rs
@@ -51,8 +51,10 @@ use std::{
};
use sum_tree::Bias;
use theme::{ActiveTheme, PlayerColor};
-use ui::prelude::*;
-use ui::{h_stack, IconButton, Tooltip};
+use ui::{
+ h_stack, ButtonLike, ButtonStyle, Disclosure, IconButton, IconElement, IconSize, Label, Tooltip,
+};
+use ui::{prelude::*, Icon};
use util::ResultExt;
use workspace::item::Item;
@@ -2223,7 +2225,8 @@ impl EditorElement {
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default();
- let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
+
+ let jump_handler = project::File::from_dyn(buffer.file()).map(|file| {
let jump_path = ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
@@ -2234,11 +2237,11 @@ impl EditorElement {
.map_or(range.context.start, |primary| primary.start);
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
- IconButton::new(block_id, ui::Icon::ArrowUpRight)
- .on_click(cx.listener_for(&self.editor, move |editor, e, cx| {
- editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
- }))
- .tooltip(|cx| Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx))
+ let jump_handler = cx.listener_for(&self.editor, move |editor, e, cx| {
+ editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
+ });
+
+ jump_handler
});
let element = if *starts_new_buffer {
@@ -2253,25 +2256,108 @@ impl EditorElement {
.map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
}
- h_stack()
- .id("path header block")
- .size_full()
- .bg(gpui::red())
- .child(
- filename
- .map(SharedString::from)
- .unwrap_or_else(|| "untitled".into()),
- )
- .children(parent_path)
- .children(jump_icon) // .p_x(gutter_padding)
+ let is_open = true;
+
+ div().id("path header container").size_full().p_1p5().child(
+ h_stack()
+ .id("path header block")
+ .py_1p5()
+ .pl_3()
+ .pr_2()
+ .rounded_lg()
+ .shadow_md()
+ .border()
+ .border_color(cx.theme().colors().border)
+ .bg(cx.theme().colors().editor_subheader_background)
+ .justify_between()
+ .cursor_pointer()
+ .hover(|style| style.bg(cx.theme().colors().element_hover))
+ .on_click(cx.listener(|_editor, _event, _cx| {
+ // TODO: Implement collapsing path headers
+ todo!("Clicking path header")
+ }))
+ .child(
+ h_stack()
+ .gap_3()
+ // TODO: Add open/close state and toggle action
+ .child(
+ div().border().border_color(gpui::red()).child(
+ ButtonLike::new("path-header-disclosure-control")
+ .style(ButtonStyle::Subtle)
+ .child(IconElement::new(match is_open {
+ true => Icon::ChevronDown,
+ false => Icon::ChevronRight,
+ })),
+ ),
+ )
+ .child(
+ h_stack()
+ .gap_2()
+ .child(Label::new(
+ filename
+ .map(SharedString::from)
+ .unwrap_or_else(|| "untitled".into()),
+ ))
+ .when_some(parent_path, |then, path| {
+ then.child(Label::new(path).color(Color::Muted))
+ }),
+ ),
+ )
+ .children(jump_handler.map(|jump_handler| {
+ IconButton::new(block_id, Icon::ArrowUpRight)
+ .style(ButtonStyle::Subtle)
+ .on_click(jump_handler)
+ .tooltip(|cx| {
+ Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx)
+ })
+ })), // .p_x(gutter_padding)
+ )
} else {
let text_style = style.text.clone();
h_stack()
.id("collapsed context")
.size_full()
- .bg(gpui::red())
- .child("⋯")
- .children(jump_icon) // .p_x(gutter_padding)
+ .gap(gutter_padding)
+ .child(
+ h_stack()
+ .justify_end()
+ .flex_none()
+ .w(gutter_width - gutter_padding)
+ .h_full()
+ .text_buffer(cx)
+ .text_color(cx.theme().colors().editor_line_number)
+ .child("..."),
+ )
+ .map(|this| {
+ if let Some(jump_handler) = jump_handler {
+ this.child(
+ ButtonLike::new("jump to collapsed context")
+ .style(ButtonStyle::Transparent)
+ .full_width()
+ .on_click(jump_handler)
+ .tooltip(|cx| {
+ Tooltip::for_action(
+ "Jump to Buffer",
+ &OpenExcerpts,
+ cx,
+ )
+ })
+ .child(
+ div()
+ .h_px()
+ .w_full()
+ .bg(cx.theme().colors().border_variant)
+ .group_hover("", |style| {
+ style.bg(cx.theme().colors().border)
+ }),
+ ),
+ )
+ } else {
+ this.child(div().size_full().bg(gpui::green()))
+ }
+ })
+ // .child("⋯")
+ // .children(jump_icon) // .p_x(gutter_padding)
};
element.into_any()
}
diff --git a/crates/ui2/src/components/button/button.rs b/crates/ui2/src/components/button/button.rs
index d5f8ce9287..c1262321ce 100644
--- a/crates/ui2/src/components/button/button.rs
+++ b/crates/ui2/src/components/button/button.rs
@@ -1,4 +1,4 @@
-use gpui::AnyView;
+use gpui::{AnyView, DefiniteLength};
use crate::prelude::*;
use crate::{
@@ -88,6 +88,18 @@ impl Clickable for Button {
}
}
+impl FixedWidth for Button {
+ fn width(mut self, width: DefiniteLength) -> Self {
+ self.base = self.base.width(width);
+ self
+ }
+
+ fn full_width(mut self) -> Self {
+ self.base = self.base.full_width();
+ self
+ }
+}
+
impl ButtonCommon for Button {
fn id(&self) -> &ElementId {
self.base.id()
diff --git a/crates/ui2/src/components/button/button_like.rs b/crates/ui2/src/components/button/button_like.rs
index 71aa31ced2..4bef6bff77 100644
--- a/crates/ui2/src/components/button/button_like.rs
+++ b/crates/ui2/src/components/button/button_like.rs
@@ -1,3 +1,4 @@
+use gpui::{relative, DefiniteLength};
use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
use smallvec::SmallVec;
@@ -246,6 +247,7 @@ pub struct ButtonLike {
pub(super) style: ButtonStyle,
pub(super) disabled: bool,
pub(super) selected: bool,
+ pub(super) width: Option,
size: ButtonSize,
tooltip: Option AnyView>>,
on_click: Option>,
@@ -259,6 +261,7 @@ impl ButtonLike {
style: ButtonStyle::default(),
disabled: false,
selected: false,
+ width: None,
size: ButtonSize::Default,
tooltip: None,
children: SmallVec::new(),
@@ -288,6 +291,18 @@ impl Clickable for ButtonLike {
}
}
+impl FixedWidth for ButtonLike {
+ fn width(mut self, width: DefiniteLength) -> Self {
+ self.width = Some(width);
+ self
+ }
+
+ fn full_width(mut self) -> Self {
+ self.width = Some(relative(1.));
+ self
+ }
+}
+
impl ButtonCommon for ButtonLike {
fn id(&self) -> &ElementId {
&self.id
@@ -321,7 +336,10 @@ impl RenderOnce for ButtonLike {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
h_stack()
.id(self.id.clone())
+ .group("")
+ .flex_none()
.h(self.size.height())
+ .when_some(self.width, |this, width| this.w(width))
.rounded_md()
.gap_1()
.px_1()
diff --git a/crates/ui2/src/components/button/icon_button.rs b/crates/ui2/src/components/button/icon_button.rs
index 981eb3aaca..94431ef642 100644
--- a/crates/ui2/src/components/button/icon_button.rs
+++ b/crates/ui2/src/components/button/icon_button.rs
@@ -1,4 +1,4 @@
-use gpui::{Action, AnyView};
+use gpui::{Action, AnyView, DefiniteLength};
use crate::prelude::*;
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
@@ -69,6 +69,18 @@ impl Clickable for IconButton {
}
}
+impl FixedWidth for IconButton {
+ fn width(mut self, width: DefiniteLength) -> Self {
+ self.base = self.base.width(width);
+ self
+ }
+
+ fn full_width(mut self) -> Self {
+ self.base = self.base.full_width();
+ self
+ }
+}
+
impl ButtonCommon for IconButton {
fn id(&self) -> &ElementId {
self.base.id()
diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs
index 05dac731dd..a993a54e15 100644
--- a/crates/ui2/src/components/icon.rs
+++ b/crates/ui2/src/components/icon.rs
@@ -27,6 +27,7 @@ pub enum Icon {
Bolt,
CaseSensitive,
Check,
+ Copy,
ChevronDown,
ChevronLeft,
ChevronRight,
@@ -100,6 +101,7 @@ impl Icon {
Icon::Bolt => "icons/bolt.svg",
Icon::CaseSensitive => "icons/case_insensitive.svg",
Icon::Check => "icons/check.svg",
+ Icon::Copy => "icons/copy.svg",
Icon::ChevronDown => "icons/chevron_down.svg",
Icon::ChevronLeft => "icons/chevron_left.svg",
Icon::ChevronRight => "icons/chevron_right.svg",
diff --git a/crates/ui2/src/styled_ext.rs b/crates/ui2/src/styled_ext.rs
index cb224fd0fe..e567830d6c 100644
--- a/crates/ui2/src/styled_ext.rs
+++ b/crates/ui2/src/styled_ext.rs
@@ -1,4 +1,6 @@
use gpui::{px, Styled, WindowContext};
+use settings::Settings;
+use theme::ThemeSettings;
use crate::prelude::*;
use crate::{ElevationIndex, UITextSize};
@@ -60,6 +62,18 @@ pub trait StyledExt: Styled + Sized {
self.text_size(size)
}
+ /// The font size for buffer text.
+ ///
+ /// Retrieves the default font size, or the user's custom font size if set.
+ ///
+ /// This should only be used for text that is displayed in a buffer,
+ /// or other places that text needs to match the user's buffer font size.
+ fn text_buffer(self, cx: &mut WindowContext) -> Self {
+ let settings = ThemeSettings::get_global(cx);
+
+ self.text_size(settings.buffer_font_size)
+ }
+
/// The [`Surface`](ui2::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
///
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`