Compare commits

...

26 Commits

Author SHA1 Message Date
Julia Ryan
f8f5028323 snapshot 2025-05-12 09:29:58 +02:00
Nate Butler
7bbe256e92 Merge branch 'main' into add-walkthrough 2025-05-08 07:14:42 -04:00
Julia Ryan
f7ab9143e0 wip
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-05-07 16:42:36 -07:00
Julia Ryan
5bccbfb7e3 Add scrollbar helper
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-05-07 16:42:02 -07:00
Julia Ryan
0d2d1a94ca Remove Ui and Editor from TextSize
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-05-07 16:40:35 -07:00
Julia Ryan
3c98065f09 Add AI config section
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-05-07 14:31:51 -07:00
Julia Ryan
513e078a36 Add telemetry buttons 2025-05-07 14:31:07 -07:00
Julia Ryan
27f1ee9834 Cleanup and project-opening attempts 2025-05-07 14:31:07 -07:00
Julia Ryan
f2ae55503f Improve styling and attempt recent-project opening 2025-05-07 14:31:07 -07:00
Julia Ryan
0214bed452 Add sublime recent-projects 2025-05-07 14:31:07 -07:00
Julia Ryan
f0070913d5 Fix project-finding logic 2025-05-07 14:31:06 -07:00
Julia Ryan
fde482f5df Wire up more buttons 2025-05-07 14:31:06 -07:00
Julia Ryan
525030751e Get it compiling again 2025-05-07 14:31:06 -07:00
Julia Ryan
08043c1954 Add progress for recent projects and settings steps 2025-05-07 14:31:06 -07:00
Mikayla Maki
5d51380f61 Fix walkthrough data structure to be actually useful
Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-05-07 14:31:06 -07:00
Julia Ryan
331afce18c Use transparent tabs for theme preview 2025-05-07 14:31:06 -07:00
Mikayla Maki
caf4127d19 Add a transparent tab component
Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-05-07 14:31:06 -07:00
Mikayla Maki
78cc4bc6c1 Start incorporating the theme preview into the walkthrough onboarding 2025-05-07 14:31:06 -07:00
Nate Butler
37d8082f76 welcome: Theme preview tile (#29689)
![CleanShot 2025-04-30 at 13 26
44@2x](https://github.com/user-attachments/assets/f68fefe2-84a1-48b7-b9a2-47c2547cd06b)

- Adds the ThemePreviewTile component, used for upcoming onboarding UI
- Adds the CornerSolver utility for resolving correct nested corner
radii

Release Notes:

- N/A
2025-05-07 14:31:06 -07:00
Julia Ryan
956c0750d4 Plan out more walkthrough content 2025-05-07 14:31:06 -07:00
Julia Ryan
03cb4361ce Style the walkthrough UI list elements
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-05-07 14:31:06 -07:00
Julia Ryan
02c578d9ac wip 2025-05-07 14:31:06 -07:00
Julia Ryan
4d62c7985b recent project notes 2025-05-07 14:31:05 -07:00
Julia Ryan
e95c6ff5bc Start on theme picker 2025-05-07 14:29:52 -07:00
Mikayla Maki
92334bffc7 Implement basic walkthrough structure
Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-05-07 14:29:52 -07:00
Mikayla Maki
1ec87176d0 Add a basic serialized walkthrough page
Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-05-07 14:29:52 -07:00
55 changed files with 1445 additions and 468 deletions

8
Cargo.lock generated
View File

@@ -17034,18 +17034,26 @@ dependencies = [
"db",
"documented",
"editor",
"fs",
"fuzzy",
"gpui",
"install_cli",
"language",
"language_model",
"linkme",
"paths",
"picker",
"project",
"regex",
"schemars",
"serde",
"serde_json",
"settings",
"smol",
"telemetry",
"theme",
"time",
"time_format",
"ui",
"util",
"vim_mode_setting",

View File

@@ -44,8 +44,7 @@ use std::time::Duration;
use text::ToPoint;
use theme::ThemeSettings;
use ui::{
Disclosure, IconButton, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
Tooltip, prelude::*,
prelude::*, scrollbar, Disclosure, IconButton, KeyBinding, PopoverMenuHandle, ScrollbarState, TextSize, Tooltip
};
use util::ResultExt as _;
use util::markdown::MarkdownCodeBlock;
@@ -179,8 +178,8 @@ fn parse_markdown(
pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
let theme_settings = ThemeSettings::get_global(cx);
let colors = cx.theme().colors();
let ui_font_size = TextSize::Default.rems(cx);
let buffer_font_size = TextSize::Small.rems(cx);
let ui_font_size = TextSize::Default.rems();
let buffer_font_size = TextSize::Small.rems();
let mut text_style = window.text_style();
text_style.refine(&TextStyleRefinement {
@@ -276,8 +275,8 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle
fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
let theme_settings = ThemeSettings::get_global(cx);
let colors = cx.theme().colors();
let ui_font_size = TextSize::Default.rems(cx);
let buffer_font_size = TextSize::Small.rems(cx);
let ui_font_size = TextSize::Default.rems();
let buffer_font_size = TextSize::Small.rems();
let mut text_style = window.text_style();
text_style.refine(&TextStyleRefinement {
@@ -313,7 +312,7 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
font_family: Some(theme_settings.buffer_font.family.clone()),
font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
font_features: Some(theme_settings.buffer_font.features.clone()),
font_size: Some(TextSize::XSmall.rems(cx).into()),
font_size: Some(TextSize::XSmall.rems().into()),
..Default::default()
},
heading: StyleRefinement {
@@ -1698,7 +1697,7 @@ impl ActiveThread {
) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let font_size = TextSize::Small
.rems(cx)
.rems()
.to_pixels(settings.agent_font_size(cx));
let line_height = font_size * 1.75;
@@ -2284,7 +2283,7 @@ impl ActiveThread {
let is_user_message = message_role == Role::User;
v_flex()
.text_ui(cx)
.text_ui()
.gap_2()
.when(is_user_message, |this| this.text_xs())
.children(
@@ -2313,7 +2312,7 @@ impl ActiveThread {
let theme_settings = ThemeSettings::get_global(cx);
let buffer_font = theme_settings.buffer_font.family.clone();
let buffer_font_size = TextSize::Small.rems(cx);
let buffer_font_size = TextSize::Small.rems();
text_style.refine(&TextStyleRefinement {
font_family: Some(buffer_font),
@@ -2525,7 +2524,7 @@ impl ActiveThread {
.id(("thinking-content", ix))
.max_h_20()
.track_scroll(scroll_handle)
.text_ui_sm(cx)
.text_ui_sm()
.overflow_hidden()
.child(
MarkdownElement::new(
@@ -2554,7 +2553,7 @@ impl ActiveThread {
.id(("thinking-content", ix))
.h_full()
.bg(editor_bg)
.text_ui_sm(cx)
.text_ui_sm()
.child(
MarkdownElement::new(
markdown.clone(),
@@ -2616,7 +2615,7 @@ impl ActiveThread {
.pl_2p5()
.border_l_1()
.border_color(cx.theme().colors().border_variant)
.text_ui_sm(cx)
.text_ui_sm()
.when(is_open, |this| {
this.child(
MarkdownElement::new(
@@ -2694,21 +2693,19 @@ impl ActiveThread {
let rendered_tool_use = self.rendered_tool_uses.get(&tool_use.id).cloned();
let results_content_container = || v_flex().p_2().gap_0p5();
let results_content = v_flex()
.gap_1()
.child(
results_content_container()
.child(
Label::new("Input")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
)
.child(
div()
.w_full()
.text_ui_sm(cx)
.children(rendered_tool_use.as_ref().map(|rendered| {
let results_content =
v_flex()
.gap_1()
.child(
results_content_container()
.child(
Label::new("Input")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
)
.child(div().w_full().text_ui_sm().children(
rendered_tool_use.as_ref().map(|rendered| {
MarkdownElement::new(
rendered.input.clone(),
tool_use_markdown_style(window, cx),
@@ -2723,83 +2720,81 @@ impl ActiveThread {
open_markdown_link(text, workspace.clone(), window, cx);
}
})
})),
),
)
.map(|container| match tool_use.status {
ToolUseStatus::Finished(_) => container.child(
results_content_container()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
Label::new("Result")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
)
.child(div().w_full().text_ui_sm(cx).children(
rendered_tool_use.as_ref().map(|rendered| {
MarkdownElement::new(
rendered.output.clone(),
tool_use_markdown_style(window, cx),
)
.code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false,
border: false,
})
.on_url_click({
let workspace = self.workspace.clone();
move |text, window, cx| {
open_markdown_link(text, workspace.clone(), window, cx);
}
})
.into_any_element()
}),
)),
),
ToolUseStatus::InputStillStreaming | ToolUseStatus::Running => container.child(
results_content_container()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
h_flex()
.gap_1()
.child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::Small)
.color(Color::Accent)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| {
icon.transform(Transformation::rotate(percentage(
delta,
)))
},
),
)
.child(
Label::new("Running…")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
),
),
),
ToolUseStatus::Error(_) => container.child(
results_content_container()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
Label::new("Error")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
)
.child(
div()
.text_ui_sm(cx)
.children(rendered_tool_use.as_ref().map(|rendered| {
)
.map(|container| match tool_use.status {
ToolUseStatus::Finished(_) => container.child(
results_content_container()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
Label::new("Result")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
)
.child(div().w_full().text_ui_sm().children(
rendered_tool_use.as_ref().map(|rendered| {
MarkdownElement::new(
rendered.output.clone(),
tool_use_markdown_style(window, cx),
)
.code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false,
border: false,
})
.on_url_click({
let workspace = self.workspace.clone();
move |text, window, cx| {
open_markdown_link(text, workspace.clone(), window, cx);
}
})
.into_any_element()
}),
)),
),
ToolUseStatus::InputStillStreaming | ToolUseStatus::Running => container.child(
results_content_container()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
h_flex()
.gap_1()
.child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::Small)
.color(Color::Accent)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| {
icon.transform(Transformation::rotate(
percentage(delta),
))
},
),
)
.child(
Label::new("Running…")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
),
),
),
ToolUseStatus::Error(_) => container.child(
results_content_container()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
Label::new("Error")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
)
.child(div().text_ui_sm().children(rendered_tool_use.as_ref().map(
|rendered| {
MarkdownElement::new(
rendered.output.clone(),
tool_use_markdown_style(window, cx),
@@ -2811,22 +2806,22 @@ impl ActiveThread {
}
})
.into_any_element()
})),
),
),
ToolUseStatus::Pending => container,
ToolUseStatus::NeedsConfirmation => container.child(
results_content_container()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
Label::new("Asking Permission")
.size(LabelSize::Small)
.color(Color::Muted)
.buffer_font(cx),
),
),
});
},
))),
),
ToolUseStatus::Pending => container,
ToolUseStatus::NeedsConfirmation => container.child(
results_content_container()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
Label::new("Asking Permission")
.size(LabelSize::Small)
.color(Color::Muted)
.buffer_font(cx),
),
),
});
let gradient_overlay = |color: Hsla| {
div()
@@ -2964,7 +2959,7 @@ impl ActiveThread {
.color(Color::Muted),
)
.child(
h_flex().pr_8().text_ui_sm(cx).children(
h_flex().pr_8().text_ui_sm().children(
rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
open_markdown_link(text, workspace.clone(), window, cx);
}}))
@@ -3326,43 +3321,12 @@ impl ActiveThread {
}
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
fn render_vertical_scrollbar(&self, window: &mut Window) -> Option<Stateful<Div>> {
if !self.show_scrollbar && !self.scrollbar_state.is_dragging() {
return None;
}
Some(
div()
.occlude()
.id("active-thread-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
Some(scrollbar(self.scrollbar_state.clone(), window))
}
fn hide_scrollbar_later(&mut self, cx: &mut Context<Self>) {
@@ -3390,7 +3354,7 @@ pub enum ActiveThreadEvent {
impl EventEmitter<ActiveThreadEvent> for ActiveThread {}
impl Render for ActiveThread {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.size_full()
.relative()
@@ -3411,7 +3375,7 @@ impl Render for ActiveThread {
}),
)
.child(list(self.list_state.clone()).flex_grow())
.when_some(self.render_vertical_scrollbar(cx), |this, scrollbar| {
.when_some(self.render_vertical_scrollbar(window), |this, scrollbar| {
this.child(scrollbar)
})
}

View File

@@ -18,8 +18,7 @@ use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageMod
use project::context_server_store::{ContextServerStatus, ContextServerStore};
use settings::{Settings, update_settings_file};
use ui::{
Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Scrollbar, ScrollbarState,
Switch, SwitchColor, Tooltip, prelude::*,
prelude::*, scrollbar, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip
};
use util::ResultExt as _;
use zed_actions::ExtensionCategoryFilter;
@@ -593,31 +592,6 @@ impl Render for AgentConfiguration {
.child(Divider::horizontal().color(DividerColor::Border))
.child(self.render_provider_configuration_section(cx)),
)
.child(
div()
.id("assistant-configuration-scrollbar")
.occlude()
.absolute()
.right(px(3.))
.top_0()
.bottom_0()
.pb_6()
.w(px(12.))
.cursor_default()
.on_mouse_move(cx.listener(|_, _, _window, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _window, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _window, cx| {
cx.stop_propagation();
})
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
.child(scrollbar(self.scrollbar_state.clone(), window))
}
}

View File

@@ -515,7 +515,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle
font_family: Some(theme_settings.ui_font.family.clone()),
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
font_features: Some(theme_settings.ui_font.features.clone()),
font_size: Some(TextSize::XSmall.rems(cx).into()),
font_size: Some(TextSize::XSmall.rems().into()),
color: Some(colors.text_muted),
..Default::default()
});

View File

@@ -759,7 +759,7 @@ impl<T: 'static> PromptEditor<T> {
}
fn render_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
let font_size = TextSize::Default.rems(cx);
let font_size = TextSize::Default.rems();
let line_height = font_size.to_pixels(window.rem_size()) * 1.3;
div()

View File

@@ -629,7 +629,7 @@ impl MessageEditor {
.child({
let settings = ThemeSettings::get_global(cx);
let font_size = TextSize::Small
.rems(cx)
.rems()
.to_pixels(settings.agent_font_size(cx));
let line_height = settings.buffer_line_height.value() * font_size;
@@ -1343,7 +1343,7 @@ impl Render for MessageEditor {
let action_log = self.thread.read(cx).action_log();
let changed_buffers = action_log.read(cx).changed_buffers(cx);
let line_height = TextSize::Small.rems(cx).to_pixels(window.rem_size()) * 1.5;
let line_height = TextSize::Small.rems().to_pixels(window.rem_size()) * 1.5;
v_flex()
.size_full()

View File

@@ -13,7 +13,7 @@ use gpui::{
use time::{OffsetDateTime, UtcOffset};
use ui::{
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
Tooltip, prelude::*,
Tooltip, prelude::*, scrollbar,
};
use util::ResultExt;
@@ -340,40 +340,21 @@ impl ThreadHistory {
cx.notify();
}
fn render_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
fn render_scrollbar(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Stateful<Div>> {
if !(self.scrollbar_visibility || self.scrollbar_state.is_dragging()) {
return None;
}
Some(
div()
.occlude()
.id("thread-history-scroll")
.h_full()
scrollbar(self.scrollbar_state.clone(), window)
.bg(cx.theme().colors().panel_background.opacity(0.8))
.border_l_1()
.border_color(cx.theme().colors().border_variant)
.absolute()
.right_1()
.top_0()
.bottom_0()
.w_4()
.pl_1()
.cursor_default()
.on_mouse_move(cx.listener(|_, _, _window, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _window, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _window, cx| {
cx.stop_propagation();
})
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
.pl_1(),
)
}
@@ -529,7 +510,7 @@ impl Focusable for ThreadHistory {
}
impl Render for ThreadHistory {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.key_context("ThreadHistory")
.size_full()
@@ -592,7 +573,7 @@ impl Render for ThreadHistory {
.track_scroll(self.scroll_handle.clone())
.flex_grow(),
)
.when_some(self.render_scrollbar(cx), |div, scrollbar| {
.when_some(self.render_scrollbar(window, cx), |div, scrollbar| {
div.child(scrollbar)
})
}

View File

@@ -100,7 +100,7 @@ impl Render for AgentNotification {
.gap_4()
.justify_between()
.elevation_3(cx)
.text_ui(cx)
.text_ui()
.font(ui_font)
.border_color(cx.theme().colors().border)
.rounded_xl()

View File

@@ -1367,7 +1367,7 @@ impl ContextEditor {
.items_center()
.gap_1()
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::XSmall.rems(cx))
.text_size(TextSize::XSmall.rems())
.text_color(colors.text_muted)
.child("Press")
.child(

View File

@@ -577,7 +577,7 @@ impl ToolCard for EditFileToolCard {
editor.set_text_style_refinement(TextStyleRefinement {
font_size: Some(
TextSize::Small
.rems(cx)
.rems()
.to_pixels(ThemeSettings::get_global(cx).agent_font_size(cx))
.into(),
),
@@ -682,7 +682,7 @@ impl ToolCard for EditFileToolCard {
.child(
div()
.rounded_md()
.text_ui_sm(cx)
.text_ui_sm()
.bg(cx.theme().colors().editor_background)
.children(
error_message

View File

@@ -586,7 +586,7 @@ impl ToolCard for TerminalToolCard {
.border_color(border_color)
.bg(cx.theme().colors().editor_background)
.rounded_b_md()
.text_ui_sm(cx)
.text_ui_sm()
.child(terminal.clone()),
)
})

View File

@@ -44,7 +44,7 @@ impl Render for Breadcrumbs {
.id("breadcrumb-container")
.flex_grow()
.overflow_x_scroll()
.text_ui(cx);
.text_ui();
let Some(active_item) = self.active_item.as_ref() else {
return element;

View File

@@ -319,7 +319,7 @@ impl ChatPanel {
None => {
return div().child(
h_flex()
.text_ui_xs(cx)
.text_ui_xs()
.my_0p5()
.px_0p5()
.gap_x_1()
@@ -354,7 +354,7 @@ impl ChatPanel {
div().child(
h_flex()
.id(message_element_id)
.text_ui_xs(cx)
.text_ui_xs()
.my_0p5()
.px_0p5()
.gap_x_1()
@@ -504,7 +504,7 @@ impl ChatPanel {
this.child(
h_flex()
.gap_2()
.text_ui_sm(cx)
.text_ui_sm()
.child(
Avatar::new(message.sender.avatar_uri.clone())
.size(rems(1.)),
@@ -541,7 +541,7 @@ impl ChatPanel {
el.child(
v_flex()
.w_full()
.text_ui_sm(cx)
.text_ui_sm()
.id(element_id)
.child(text.element("body".into(), window, cx)),
)
@@ -564,7 +564,7 @@ impl ChatPanel {
div()
.px_1()
.rounded_sm()
.text_ui_xs(cx)
.text_ui_xs()
.bg(cx.theme().colors().background)
.child("New messages"),
)
@@ -1011,7 +1011,7 @@ impl Render for ChatPanel {
el.child(
h_flex()
.px_2()
.text_ui_xs(cx)
.text_ui_xs()
.justify_between()
.border_t_1()
.border_color(cx.theme().colors().border)

View File

@@ -524,7 +524,7 @@ impl Render for MessageEditor {
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: TextSize::Small.rems(cx).into(),
font_size: TextSize::Small.rems().into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),

View File

@@ -34,7 +34,7 @@ impl ParentElement for CollabNotification {
impl RenderOnce for CollabNotification {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
h_flex()
.text_ui(cx)
.text_ui()
.justify_between()
.size_full()
.overflow_hidden()

View File

@@ -629,7 +629,7 @@ impl ComponentPreview {
.when_some(description, |this, description| {
this.child(
div()
.text_ui_sm(cx)
.text_ui_sm()
.text_color(cx.theme().colors().text_muted)
.max_w(px(600.0))
.child(description),

View File

@@ -21,8 +21,8 @@ use project::{
use ui::{
App, Clickable, Color, Context, Div, Icon, IconButton, IconName, Indicator, InteractiveElement,
IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement, Render, RenderOnce,
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Window, div,
h_flex, px, v_flex,
ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Window, div, h_flex, px,
scrollbar, v_flex,
};
use util::{ResultExt, maybe};
use workspace::Workspace;
@@ -103,48 +103,17 @@ impl BreakpointList {
}))
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
fn render_vertical_scrollbar(&self, window: &mut Window) -> Option<Stateful<Div>> {
if !(self.show_scrollbar || self.scrollbar_state.is_dragging()) {
return None;
}
Some(
div()
.occlude()
.id("breakpoint-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
Some(scrollbar(self.scrollbar_state.clone(), window))
}
}
impl Render for BreakpointList {
fn render(
&mut self,
_window: &mut ui::Window,
window: &mut ui::Window,
cx: &mut ui::Context<Self>,
) -> impl ui::IntoElement {
let old_len = self.breakpoints.len();
@@ -227,7 +196,7 @@ impl Render for BreakpointList {
.size_full()
.m_0p5()
.child(list(self.list_state.clone()).flex_grow())
.children(self.render_vertical_scrollbar(cx))
.children(self.render_vertical_scrollbar(window))
}
}
#[derive(Clone, Debug)]

View File

@@ -64,12 +64,12 @@ impl LoadedSourceList {
.child(
h_flex()
.gap_0p5()
.text_ui_sm(cx)
.text_ui_sm()
.when_some(source.name.clone(), |this, name| this.child(name)),
)
.child(
h_flex()
.text_ui_xs(cx)
.text_ui_xs()
.text_color(cx.theme().colors().text_muted)
.when_some(source.path.clone(), |this, path| this.child(path)),
)

View File

@@ -1,14 +1,13 @@
use anyhow::anyhow;
use gpui::{
AnyElement, Empty, Entity, FocusHandle, Focusable, ListState, MouseButton, Stateful,
Subscription, WeakEntity, list,
AnyElement, Empty, Entity, FocusHandle, Focusable, ListState, Subscription, WeakEntity, list,
};
use project::{
ProjectItem as _, ProjectPath,
debugger::session::{Session, SessionEvent},
};
use std::{path::Path, sync::Arc};
use ui::{Scrollbar, ScrollbarState, prelude::*};
use ui::{ScrollbarState, prelude::*, scrollbar};
use util::maybe;
use workspace::Workspace;
@@ -141,10 +140,10 @@ impl ModuleList {
})
.p_1()
.hover(|s| s.bg(cx.theme().colors().element_hover))
.child(h_flex().gap_0p5().text_ui_sm(cx).child(module.name.clone()))
.child(h_flex().gap_0p5().text_ui_sm().child(module.name.clone()))
.child(
h_flex()
.text_ui_xs(cx)
.text_ui_xs()
.text_color(cx.theme().colors().text_muted)
.when_some(module.path.clone(), |this, path| this.child(path)),
)
@@ -156,38 +155,6 @@ impl ModuleList {
self.session
.update(cx, |session, cx| session.modules(cx).to_vec())
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("module-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}
impl Focusable for ModuleList {
@@ -197,7 +164,7 @@ impl Focusable for ModuleList {
}
impl Render for ModuleList {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self.invalidate {
let len = self
.session
@@ -212,6 +179,6 @@ impl Render for ModuleList {
.size_full()
.p_1()
.child(list(self.list.clone()).size_full())
.child(self.render_vertical_scrollbar(cx))
.child(scrollbar(self.scrollbar_state.clone(), window))
}
}

View File

@@ -5,15 +5,15 @@ use std::time::Duration;
use anyhow::{Result, anyhow};
use dap::StackFrameId;
use gpui::{
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, MouseButton, Stateful,
Subscription, Task, WeakEntity, list,
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, Subscription, Task,
WeakEntity, list,
};
use language::PointUtf16;
use project::debugger::breakpoint_store::ActiveStackFrame;
use project::debugger::session::{Session, SessionEvent, StackFrame};
use project::{ProjectItem, ProjectPath};
use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*};
use ui::{ScrollbarState, Tooltip, prelude::*, scrollbar};
use util::ResultExt;
use workspace::Workspace;
@@ -488,7 +488,7 @@ impl StackFrameList {
.hover(|style| style.bg(cx.theme().colors().element_hover).cursor_pointer())
.child(
v_flex()
.text_ui_sm(cx)
.text_ui_sm()
.truncate()
.text_color(cx.theme().colors().text_muted)
.child(format!(
@@ -512,49 +512,16 @@ impl StackFrameList {
}
}
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("stack-frame-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}
impl Render for StackFrameList {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div()
.track_focus(&self.focus_handle)
.size_full()
.p_1()
.child(list(self.list.clone()).size_full())
.child(self.render_vertical_scrollbar(cx))
.child(scrollbar(self.scrollbar_state.clone(), window))
}
}

View File

@@ -3,13 +3,13 @@ use dap::{ScopePresentationHint, StackFrameId, VariablePresentationHintKind, Var
use editor::Editor;
use gpui::{
AnyElement, ClickEvent, ClipboardItem, Context, DismissEvent, Entity, FocusHandle, Focusable,
Hsla, MouseButton, MouseDownEvent, Point, Stateful, Subscription, TextStyleRefinement,
UniformListScrollHandle, actions, anchored, deferred, uniform_list,
Hsla, MouseDownEvent, Point, Subscription, TextStyleRefinement, UniformListScrollHandle,
actions, anchored, deferred, uniform_list,
};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};
use project::debugger::session::{Session, SessionEvent};
use std::{collections::HashMap, ops::Range, sync::Arc};
use ui::{ContextMenu, ListItem, Scrollbar, ScrollbarState, prelude::*};
use ui::{ContextMenu, ListItem, ScrollbarState, prelude::*, scrollbar};
use util::{debug_panic, maybe};
actions!(variable_list, [ExpandSelectedEntry, CollapseSelectedEntry]);
@@ -625,12 +625,7 @@ impl VariableList {
let mut editor = Editor::single_line(window, cx);
let refinement = TextStyleRefinement {
font_size: Some(
TextSize::XSmall
.rems(cx)
.to_pixels(window.rem_size())
.into(),
),
font_size: Some(TextSize::XSmall.rems().to_pixels(window.rem_size()).into()),
..Default::default()
};
editor.set_text_style_refinement(refinement);
@@ -703,7 +698,7 @@ impl VariableList {
})
.child(
div()
.text_ui(cx)
.text_ui()
.w_full()
.when(self.disabled, |this| {
this.text_color(Color::Disabled.color(cx))
@@ -822,7 +817,7 @@ impl VariableList {
.child(
h_flex()
.gap_1()
.text_ui_sm(cx)
.text_ui_sm()
.w_full()
.child(
Label::new(&dap.name).when_some(variable_name_color, |this, color| {
@@ -885,39 +880,6 @@ impl VariableList {
)
.into_any()
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("variable-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}
impl Focusable for VariableList {
@@ -927,7 +889,7 @@ impl Focusable for VariableList {
}
impl Render for VariableList {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
self.build_entries(cx);
v_flex()
@@ -967,7 +929,7 @@ impl Render for VariableList {
)
.with_priority(1)
}))
.child(self.render_vertical_scrollbar(cx))
.child(scrollbar(self.scrollbar_state.clone(), window))
}
}

View File

@@ -7850,7 +7850,7 @@ impl Editor {
.px_0p5()
.when(is_platform_style_mac, |parent| parent.gap_0p5())
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::XSmall.rems(cx))
.text_size(TextSize::XSmall.rems())
.child(h_flex().children(ui::render_modifiers(
&accept_keystroke.modifiers,
PlatformStyle::platform(),
@@ -21243,7 +21243,7 @@ struct MissingEditPredictionKeybindingTooltip;
impl Render for MissingEditPredictionKeybindingTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
ui::tooltip_container(window, cx, |container, _, cx| {
ui::tooltip_container(window, cx, |container, _, _cx| {
container
.flex_shrink_0()
.max_w_80()
@@ -21252,7 +21252,7 @@ impl Render for MissingEditPredictionKeybindingTooltip {
.child(
v_flex()
.flex_1()
.text_ui_sm(cx)
.text_ui_sm()
.child(Label::new("Conflict with Accept Keybinding"))
.child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
)

View File

@@ -955,12 +955,12 @@ impl FileFinderDelegate {
let (normal_em, small_em) = {
let style = window.text_style();
let font_id = window.text_system().resolve_font(&style.font());
let font_size = TextSize::Default.rems(cx).to_pixels(window.rem_size());
let font_size = TextSize::Default.rems().to_pixels(window.rem_size());
let normal = cx
.text_system()
.em_width(font_id, font_size)
.unwrap_or(px(16.));
let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size());
let font_size = TextSize::Small.rems().to_pixels(window.rem_size());
let small = cx
.text_system()
.em_width(font_id, font_size)

View File

@@ -244,7 +244,7 @@ impl BlameRenderer for GitBlameRenderer {
v_flex()
.elevation_2(cx)
.font(ui_font)
.text_ui(cx)
.text_ui()
.text_color(cx.theme().colors().text)
.py_1()
.px_2()

View File

@@ -397,7 +397,7 @@ fn render_conflict_buttons(
.px_1()
.child("Take Ours")
.rounded_t(rems(0.2))
.text_ui_sm(cx)
.text_ui_sm()
.hover(|this| this.bg(cx.theme().colors().element_background))
.cursor_pointer()
.on_click({
@@ -415,7 +415,7 @@ fn render_conflict_buttons(
.px_1()
.child("Take Theirs")
.rounded_t(rems(0.2))
.text_ui_sm(cx)
.text_ui_sm()
.hover(|this| this.bg(cx.theme().colors().element_background))
.cursor_pointer()
.on_click({
@@ -439,7 +439,7 @@ fn render_conflict_buttons(
.px_1()
.child("Take Both")
.rounded_t(rems(0.2))
.text_ui_sm(cx)
.text_ui_sm()
.hover(|this| this.bg(cx.theme().colors().element_background))
.cursor_pointer()
.on_click({

View File

@@ -3382,7 +3382,7 @@ impl GitPanel {
)
})
})
.text_ui_sm(cx)
.text_ui_sm()
.mx_auto()
.text_color(Color::Placeholder.color(cx)),
)

View File

@@ -4071,6 +4071,8 @@ pub enum ElementId {
NamedInteger(SharedString, u64),
/// A path
Path(Arc<std::path::Path>),
/// A source location
Location(core::panic::Location<'static>)
}
impl ElementId {
@@ -4090,6 +4092,7 @@ impl Display for ElementId {
ElementId::NamedInteger(s, i) => write!(f, "{}-{}", s, i)?,
ElementId::Uuid(uuid) => write!(f, "{}", uuid)?,
ElementId::Path(path) => write!(f, "{}", path.display())?,
ElementId::Location(location) => write!(f, "{}:{}:{}", location.file(), location.line(), location.column())?,
}
Ok(())
@@ -4174,6 +4177,12 @@ impl From<Uuid> for ElementId {
}
}
impl From<core::panic::Location<'static>> for ElementId {
fn from(value: core::panic::Location<'static>) -> Self {
Self::Location(value)
}
}
impl From<(&'static str, u32)> for ElementId {
fn from((name, id): (&'static str, u32)) -> Self {
ElementId::NamedInteger(name.into(), id.into())

View File

@@ -951,7 +951,7 @@ impl ConfigurationView {
}
impl Render for ConfigurationView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let env_var_set = self.state.read(cx).api_key_from_env;
if self.load_credentials_task.is_some() {

View File

@@ -1199,7 +1199,7 @@ impl ConfigurationView {
}
impl Render for ConfigurationView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let env_var_set = self.state.read(cx).credentials_from_env;
let creds_type = self.should_render_editor(cx).is_some();

View File

@@ -1,4 +1,4 @@
use gpui::{AnyElement, IntoElement, ParentElement, SharedString};
use gpui::{IntoElement, ParentElement, SharedString};
use ui::{ListItem, prelude::*};
/// A reusable list item component for adding LLM provider configuration instructions
@@ -38,7 +38,7 @@ impl IntoElement for InstructionListItem {
(self.button_label, self.button_link)
{
let link = button_link.clone();
h_flex().flex_wrap().child(Label::new(self.label)).child(
h_flex().flex_wrap().child(div().w_full().text_ui_sm().overflow_x_hidden().child(self.label)).child(
Button::new("link-button", button_label)
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
@@ -47,7 +47,7 @@ impl IntoElement for InstructionListItem {
.on_click(move |_, _window, cx| cx.open_url(&link)),
)
} else {
div().child(Label::new(self.label))
div().w_full().text_ui_sm().overflow_x_hidden().child(self.label)
};
div()

View File

@@ -323,7 +323,7 @@ impl PickerDelegate for OutlineViewDelegate {
.toggle_state(selected)
.child(
div()
.text_ui(cx)
.text_ui()
.pl(rems(outline_item.depth as f32))
.child(render_item(outline_item, mat.ranges(), cx)),
),

View File

@@ -2456,7 +2456,7 @@ impl OutlinePanel {
) -> Stateful<Div> {
let settings = OutlinePanelSettings::get_global(cx);
div()
.text_ui(cx)
.text_ui()
.id(item_id.clone())
.on_click({
let clicked_entry = rendered_entry.clone();

View File

@@ -80,7 +80,7 @@ pub fn panel_editor_container(_window: &mut Window, cx: &mut App) -> Div {
pub fn panel_editor_style(monospace: bool, window: &Window, cx: &App) -> EditorStyle {
let settings = ThemeSettings::get_global(cx);
let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size());
let font_size = TextSize::Small.rems().to_pixels(window.rem_size());
let (font_family, font_fallbacks, font_features, font_weight, line_height) = if monospace {
(
@@ -108,7 +108,7 @@ pub fn panel_editor_style(monospace: bool, window: &Window, cx: &App) -> EditorS
font_family,
font_fallbacks,
font_features,
font_size: TextSize::Small.rems(cx).into(),
font_size: TextSize::Small.rems().into(),
font_weight,
line_height: line_height.into(),
..Default::default()

View File

@@ -413,15 +413,28 @@ pub fn local_vscode_launch_file_relative_path() -> &'static Path {
/// Returns the path to the vscode user settings file
pub fn vscode_settings_file() -> &'static PathBuf {
static LOGS_DIR: OnceLock<PathBuf> = OnceLock::new();
let rel_path = "Code/User/settings.json";
LOGS_DIR.get_or_init(|| {
static VSCODE_SETTINGS_PATH: OnceLock<PathBuf> = OnceLock::new();
VSCODE_SETTINGS_PATH.get_or_init(|| vscode_data_dir().join("User/Settings.json"))
}
pub fn vscode_data_dir() -> &'static PathBuf {
static VSCODE_DATA_DIR: OnceLock<PathBuf> = OnceLock::new();
VSCODE_DATA_DIR.get_or_init(|| {
if cfg!(target_os = "macos") {
home_dir()
.join("Library/Application Support")
.join(rel_path)
home_dir().join("Library/Application Support/Code")
} else {
config_dir().join(rel_path)
config_dir().join("Code")
}
})
}
pub fn sublime_data_dir() -> &'static PathBuf {
static SUBLIME_DATA_DIR: OnceLock<PathBuf> = OnceLock::new();
SUBLIME_DATA_DIR.get_or_init(|| {
if cfg!(target_os = "macos") {
home_dir().join("Library/Application Support/Sublime Text")
} else {
config_dir().join("sublime-text-3") // TODO: handle 4?
}
})
}

View File

@@ -412,7 +412,7 @@ impl Render for MarkdownCell {
.flex_1()
.p_3()
.font_ui(cx)
.text_size(TextSize::Default.rems(cx))
.text_size(TextSize::Default.rems())
.children(parsed.children.iter().map(|child| {
div().relative().child(div().relative().child(
render_markdown_block(child, &mut markdown_render_context),
@@ -733,7 +733,7 @@ impl Render for RawCell {
.flex_1()
.p_3()
.font_ui(cx)
.text_size(TextSize::Default.rems(cx))
.text_size(TextSize::Default.rems())
.child(self.source.clone()),
),
)

View File

@@ -149,7 +149,7 @@ impl ProjectIndexDebugView {
let chunk = &state.chunks[ix];
div()
.text_ui(cx)
.text_ui()
.w_full()
.font(buffer_font)
.child(

View File

@@ -473,6 +473,11 @@ impl ThemeSettingsContent {
}
}
/// Sets the theme for the given appearance to the theme with the specified name.
pub fn set_static_theme(&mut self, theme_name: String) {
self.theme = Some(ThemeSelection::Static(theme_name.to_string()));
}
/// Sets the icon theme for the given appearance to the icon theme with the specified name.
pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
if let Some(selection) = self.icon_theme.as_mut() {

View File

@@ -95,7 +95,7 @@ impl RenderOnce for Callout {
.w_full()
.flex_1()
.child(message)
.text_ui_sm(cx)
.text_ui_sm()
.text_color(cx.theme().colors().text_muted),
)
}),

View File

@@ -287,7 +287,7 @@ impl RenderOnce for Key {
let single_char = self.key.len() == 1;
let size = self
.size
.unwrap_or_else(|| TextSize::default().rems(cx).into());
.unwrap_or_else(|| TextSize::default().rems().into());
div()
.py_0()

View File

@@ -169,7 +169,7 @@ impl RenderOnce for KeybindingHint {
let size = self
.size
.unwrap_or(TextSize::Small.rems(cx).to_pixels(window.rem_size()));
.unwrap_or(TextSize::Small.rems().to_pixels(window.rem_size()));
let kb_size = size - px(2.0);
let mut base = h_flex();

View File

@@ -213,10 +213,10 @@ impl RenderOnce for LabelLike {
self.base
.map(|this| match self.size {
LabelSize::Large => this.text_ui_lg(cx),
LabelSize::Default => this.text_ui(cx),
LabelSize::Small => this.text_ui_sm(cx),
LabelSize::XSmall => this.text_ui_xs(cx),
LabelSize::Large => this.text_ui_lg(),
LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(),
LabelSize::XSmall => this.text_ui_xs(),
})
.when(self.line_height_style == LineHeightStyle::UiLabel, |this| {
this.line_height(relative(1.))

View File

@@ -43,7 +43,7 @@ impl RenderOnce for AlertModal {
.p_5()
.child(
v_flex()
.text_ui(cx)
.text_ui()
.text_color(Color::Muted.color(cx))
.gap_1()
.child(Headline::new(self.title).size(HeadlineSize::Small))

View File

@@ -3,11 +3,37 @@ use std::{any::Any, cell::Cell, fmt::Debug, ops::Range, rc::Rc, sync::Arc};
use crate::{IntoElement, prelude::*, px, relative};
use gpui::{
Along, App, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Corners, Edges, Element,
ElementId, Entity, EntityId, GlobalElementId, Hitbox, Hsla, LayoutId, ListState,
ElementId, Entity, EntityId, GlobalElementId, Hitbox, Hsla, LayoutId, ListState, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, ScrollHandle, ScrollWheelEvent,
Size, Style, UniformListScrollHandle, Window, point, quad,
Size, Stateful, Style, UniformListScrollHandle, Window, point, quad,
};
#[track_caller]
pub fn scrollbar(state: ScrollbarState, window: &mut Window) -> Stateful<Div> {
let id = window.current_view();
let location = core::panic::Location::caller();
div()
.id(*location)
.occlude()
.absolute()
.right_1()
.top_0()
.bottom_0()
.pb_6()
.w_3()
.h_full()
.cursor_default()
.on_mouse_move(move |_, _window, cx| {
cx.notify(id);
cx.stop_propagation()
})
.on_hover(|_, _window, cx| cx.stop_propagation())
.on_any_mouse_down(|_, _window, cx| cx.stop_propagation())
.on_scroll_wheel(move |_, _window, cx| cx.notify(id))
.on_mouse_up(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.children(Scrollbar::vertical(state.clone()))
}
pub struct Scrollbar {
thumb: Range<f32>,
state: ScrollbarState,

View File

@@ -49,12 +49,12 @@ impl Table {
self
}
fn base_cell_style(cx: &mut App) -> Div {
fn base_cell_style() -> Div {
div()
.px_1p5()
.flex_1()
.justify_start()
.text_ui(cx)
.text_ui()
.whitespace_nowrap()
.text_ellipsis()
.overflow_hidden()
@@ -85,7 +85,7 @@ impl RenderOnce for Table {
.border_b_1()
.border_color(cx.theme().colors().border)
.children(self.column_headers.into_iter().map(|h| {
Self::base_cell_style(cx)
Self::base_cell_style()
.font_weight(FontWeight::SEMIBOLD)
.child(h)
}));
@@ -111,8 +111,8 @@ impl RenderOnce for Table {
row.border_b_1().border_color(cx.theme().colors().border)
})
.children(row.into_iter().map(|cell| match cell {
TableCell::String(s) => Self::base_cell_style(cx).child(s),
TableCell::Element(e) => Self::base_cell_style(cx).child(e),
TableCell::String(s) => Self::base_cell_style().child(s),
TableCell::Element(e) => Self::base_cell_style().child(e),
}))
});

View File

@@ -182,7 +182,7 @@ pub fn tooltip_container<V, ContentsBuilder: FnOnce(Div, &mut Window, &mut Conte
v_flex()
.elevation_2(cx)
.font(ui_font)
.text_ui(cx)
.text_ui()
.text_color(cx.theme().colors().text)
.py_1()
.px_2()

View File

@@ -27,8 +27,8 @@ pub trait StyledTypography: Styled + Sized {
}
/// Sets the text size using a [`TextSize`].
fn text_ui_size(self, size: TextSize, cx: &App) -> Self {
self.text_size(size.rems(cx))
fn text_ui_size(self, size: TextSize) -> Self {
self.text_size(size.rems())
}
/// The large size for UI text.
@@ -38,8 +38,8 @@ pub trait StyledTypography: Styled + Sized {
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui` for regular-sized text.
fn text_ui_lg(self, cx: &App) -> Self {
self.text_size(TextSize::Large.rems(cx))
fn text_ui_lg(self) -> Self {
self.text_size(TextSize::Large.rems())
}
/// The default size for UI text.
@@ -49,8 +49,8 @@ pub trait StyledTypography: Styled + Sized {
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui_sm` for smaller text.
fn text_ui(self, cx: &App) -> Self {
self.text_size(TextSize::default().rems(cx))
fn text_ui(self) -> Self {
self.text_size(TextSize::default().rems())
}
/// The small size for UI text.
@@ -60,8 +60,8 @@ pub trait StyledTypography: Styled + Sized {
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui` for regular-sized text.
fn text_ui_sm(self, cx: &App) -> Self {
self.text_size(TextSize::Small.rems(cx))
fn text_ui_sm(self) -> Self {
self.text_size(TextSize::Small.rems())
}
/// The extra small size for UI text.
@@ -71,8 +71,8 @@ pub trait StyledTypography: Styled + Sized {
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui` for regular-sized text.
fn text_ui_xs(self, cx: &App) -> Self {
self.text_size(TextSize::XSmall.rems(cx))
fn text_ui_xs(self) -> Self {
self.text_size(TextSize::XSmall.rems())
}
/// The font size for buffer text.
@@ -119,29 +119,16 @@ pub enum TextSize {
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
XSmall,
/// The `ui_font_size` set by the user.
Ui,
/// The `buffer_font_size` set by the user.
Editor,
// TODO: The terminal settings will need to be passed to
// ThemeSettings before we can enable this.
//// The `terminal.font_size` set by the user.
// Terminal,
}
impl TextSize {
/// Returns the text size in rems.
pub fn rems(self, cx: &App) -> Rems {
let theme_settings = ThemeSettings::get_global(cx);
pub fn rems(self) -> Rems {
match self {
Self::Large => rems_from_px(16.),
Self::Default => rems_from_px(14.),
Self::Small => rems_from_px(12.),
Self::XSmall => rems_from_px(10.),
Self::Ui => rems_from_px(theme_settings.ui_font_size(cx).into()),
Self::Editor => rems_from_px(theme_settings.buffer_font_size(cx).into()),
}
}
}

View File

@@ -20,18 +20,26 @@ client.workspace = true
component.workspace = true
db.workspace = true
documented.workspace = true
fs.workspace = true
fuzzy.workspace = true
gpui.workspace = true
install_cli.workspace = true
language.workspace = true
language_model.workspace = true
linkme.workspace = true
paths.workspace = true
picker.workspace = true
project.workspace = true
regex.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
telemetry.workspace = true
theme.workspace = true
time.workspace = true
time_format.workspace = true
ui.workspace = true
util.workspace = true
vim_mode_setting.workspace = true

View File

@@ -0,0 +1,126 @@
use std::{
collections::BTreeSet,
ffi::OsString,
path::{Path, PathBuf},
sync::Arc,
time::{Duration, SystemTime},
};
use fs::Fs;
use serde_json::Value;
use smol::stream::{self, StreamExt};
// don't show projects from editors you haven't used it the last 100 days
const MTIME_CUTOFF: Duration = Duration::from_secs(100 * 24 * 60 * 60);
// filters out old files and sorts the remaining ones by recency
async fn recent_files(paths: Vec<PathBuf>, fs: &dyn Fs) -> Vec<String> {
let now = SystemTime::now();
stream::iter(paths.into_iter())
.flat_map(|path| stream::once_future(async move { (fs.metadata(&path).await, path) }))
.filter_map(|(metadata, path)| {
let mtime = metadata.ok()??.mtime;
if let Ok(duration) = now.duration_since(mtime.timestamp_for_user()) {
(duration < MTIME_CUTOFF).then_some(path.to_string_lossy().to_string())
} else {
None
}
})
.collect()
.await
}
async fn dir_contains_project(path: &Path, fs: &dyn Fs) -> bool {
let Ok(mut paths) = fs.read_dir(path).await else {
return false;
};
while let Some(Ok(path)) = paths.next().await {
// TODO: look for other project files, ".jj", etc
if path.file_name() == Some(&OsString::from(".git")) {
return true;
}
}
false
}
// returns a list of project roots. ignores any file paths that aren't inside the user's home directory
async fn projects_for_paths(files: &[PathBuf], fs: &dyn Fs) -> Vec<PathBuf> {
let mut project_dirs = BTreeSet::new();
let stop_at = paths::home_dir();
for mut path in files.iter().map(|p| p.as_path()) {
while let Some(parent) = path.parent() {
if !parent.starts_with(stop_at) || project_dirs.contains(parent) {
break;
}
if dir_contains_project(parent, fs).await {
project_dirs.insert(parent.to_path_buf());
}
path = parent;
}
}
project_dirs.into_iter().collect()
}
// jq .backupWorkspaces.folders[].folderUri from Code/User/globalStorage/storage.json
//
// jq -r .folder Code/User/workspaceStorage/*/workspace.json
pub async fn get_vscode_projects(fs: Arc<dyn Fs>) -> Option<Vec<String>> {
let mut read_dir = fs
.read_dir(&paths::vscode_data_dir().join("User/workspaceStorage"))
.await
.ok()?;
let mut workspaces = Vec::new();
while let Some(Ok(dir)) = read_dir.next().await {
workspaces.push(dir.join("workspace.json"))
}
let mut result = Vec::new();
for path in recent_files(workspaces, fs.as_ref()).await {
let content = fs.load(&PathBuf::from(path)).await.ok()?;
let value = serde_json::from_str::<Value>(&content).ok()?;
if let Some(s) = value.get("folder").and_then(|v| v.as_str()) {
result.push(s.strip_prefix("file://").unwrap_or(s).to_owned());
}
}
Some(result)
}
// nvim --headless -u NONE +oldfiles +exit
pub async fn get_neovim_projects(fs: Arc<dyn Fs>) -> Option<Vec<String>> {
const MAX_OLDFILES: usize = 100;
let output = util::command::new_std_command("nvim")
.args(["--headless", "-u", "NONE", "+oldfiles", "+exit"])
.output()
.ok()?
.stderr;
let paths = String::from_utf8_lossy(&output)
.lines()
.take(MAX_OLDFILES)
.filter_map(|s| s.split(": ").last().map(PathBuf::from))
.collect::<Vec<PathBuf>>();
let projects = projects_for_paths(&paths, fs.as_ref()).await;
Some(recent_files(projects, fs.as_ref()).await)
}
// jq -r '.folder_history[]' <Sublime\ Text>/Local/Session.sublime_session
// there's also an "Auto Save Session" file that may be more up to date? ignoring for now
pub async fn get_sublime_projects(fs: Arc<dyn Fs>) -> Option<Vec<String>> {
let path = paths::sublime_data_dir().join("Local/Session.sublime_session");
let mtime = fs.metadata(&path).await.ok()??.mtime;
if let Ok(duration) = SystemTime::now().duration_since(mtime.timestamp_for_user()) {
if duration > MTIME_CUTOFF {
return None;
}
}
let content = fs.load(&path).await.ok()?;
let value = serde_json::from_str::<Value>(&content).ok()?;
value
.as_object()?
.get("folder_history")?
.as_array()?
.iter()
.map(|v| v.as_str().map(|s| s.to_owned()))
.collect()
}
// rust-rover: ??? JetBrains/RustRover20*/workspace/*.xml

View File

@@ -0,0 +1,900 @@
use client::telemetry::Telemetry;
use client::TelemetrySettings;
use fs::Fs;
use gpui::{AnyView, ScrollHandle};
use gpui::{
App, Context, Entity, EventEmitter, FocusHandle, Focusable, ListSizingBehavior, ListState,
ParentElement, Render, Styled, Subscription, WeakEntity, Window, list, svg,
};
use language_model::{LanguageModelProviderName, LanguageModelRegistry};
use persistence::WALKTHROUGH_DB;
use regex::Regex;
use settings::Settings;
use settings::SettingsStore;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::LazyLock;
use std::time::SystemTime;
use theme::ThemeRegistry;
use theme::ThemeSettings;
use time::OffsetDateTime;
use time_format::TimestampFormat;
use ui::{CheckboxWithLabel, Divider, scrollbar};
use ui::{ScrollbarState, prelude::*};
use vim_mode_setting::VimModeSetting;
use workspace::OpenFiles;
use workspace::{
SerializableItem, Workspace, WorkspaceId, delete_unloaded_items,
item::{Item, ItemEvent},
register_serializable_item,
};
use zed_actions::{ExtensionCategoryFilter, Extensions, OpenKeymap, OpenSettings};
use crate::BaseKeymap;
use crate::recent_projects;
use crate::welcome_ui::{pill_tabs::PillTabs, theme_preview::ThemePreviewTile};
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, _, _cx| {
workspace.register_action(|workspace, _: &workspace::Walkthrough, window, cx| {
let walkthrough = Walkthrough::new(workspace, window, cx);
workspace.add_item_to_active_pane(Box::new(walkthrough), None, true, window, cx)
});
})
.detach();
register_serializable_item::<Walkthrough>(cx);
}
struct AiIntegrationState {
tab_selection: Entity<usize>,
model_providers: Vec<(LanguageModelProviderName, AnyView)>,
scroll_handle: ScrollHandle,
scrollbar_state: ScrollbarState,
}
enum WalkthroughStep {
Theme { tab_selection: Entity<usize> },
Settings { tab_selection: Entity<usize> },
AiIntegrations(AiIntegrationState),
DataSharing,
OpenProject { tab_selection: Entity<usize> },
}
pub struct Walkthrough {
active_step: usize,
workspace: WeakEntity<Workspace>,
fs: Arc<dyn Fs>,
focus_handle: FocusHandle,
_telemetry: Arc<Telemetry>,
list: ListState,
steps: Vec<WalkthroughStep>,
recent_projects: BTreeMap<&'static str, Vec<String>>,
vscode_settings: Option<SystemTime>,
_settings_subscription: Subscription,
}
impl Walkthrough {
pub fn section_button(
&mut self,
ix: usize,
title: &'static str,
_description: &'static str,
cx: &mut Context<Self>,
) -> AnyElement {
let active = ix == self.active_step;
let theme = cx.theme().clone();
div()
.size_full()
.p_2()
.child(
h_flex()
.rounded_md()
.size_full()
.p_4()
.border_1()
.when(active, |div| div.bg(theme.colors().element_background))
.id(title)
.on_click(cx.listener(move |walkthrough, _, _, cx| {
walkthrough.active_step = ix;
cx.notify();
}))
.border_color(theme.colors().border)
.child(
v_flex().child(Label::new(title)), // .when(active, |div| {
// div.text_sm()
// .size_full()
// .text_color(theme.colors().text_muted)
// .child(description)
// })
),
)
.into_any()
}
pub fn new(
workspace: &Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<Self> {
let this = cx.new(|cx| {
let fs = workspace.app_state().fs.clone();
let model_providers = LanguageModelRegistry::read_global(cx)
.providers()
.into_iter()
.map(|provider| (provider.name(), provider.configuration_view(window, cx)))
.collect::<Vec<_>>();
let scroll_handle = ScrollHandle::new();
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let steps = vec![
WalkthroughStep::Theme {
tab_selection: cx.new(|_| 0),
},
WalkthroughStep::Settings {
tab_selection: cx.new(|_| 0),
},
WalkthroughStep::AiIntegrations(AiIntegrationState {
tab_selection: cx.new(|_| 0),
model_providers: model_providers,
scroll_handle,
scrollbar_state,
}),
WalkthroughStep::DataSharing,
WalkthroughStep::OpenProject {
tab_selection: cx.new(|_| 0),
},
];
// look up settings files from other editors
cx.spawn({
let fs = fs.clone();
async move |this: WeakEntity<Self>, cx| {
if let Ok(Some(metadata)) = fs.metadata(paths::vscode_settings_file()).await {
this.update(cx, |this, _| {
this.vscode_settings = Some(metadata.mtime.timestamp_for_user());
})
.ok();
}
}
})
.detach();
// look up recent projects from other editors
cx.spawn({
let fs = fs.clone();
async move |this: WeakEntity<Self>, cx| {
let mut recents: BTreeMap<&str, Vec<String>> = BTreeMap::default();
use recent_projects::*;
for (name, projects) in [
("vscode", get_vscode_projects(fs.clone()).await),
("sublime", get_sublime_projects(fs.clone()).await),
("neovim", get_neovim_projects(fs.clone()).await),
] {
if let Some(projects) = projects {
if !projects.is_empty() {
recents.insert(
name,
projects.iter().take(10).map(Clone::clone).collect(),
);
}
}
}
this.update(cx, |this, _cx| {
this.recent_projects = recents;
})
}
})
.detach();
let steps_len = steps.len();
let this = cx.weak_entity();
Walkthrough {
focus_handle: cx.focus_handle(),
workspace: workspace.weak_handle(),
fs,
_telemetry: workspace.client().telemetry().clone(),
_settings_subscription: cx
.observe_global::<SettingsStore>(move |_: &mut Walkthrough, cx| cx.notify()),
steps,
list: ListState::new(
steps_len,
gpui::ListAlignment::Top,
px(1000.),
move |ix, _window, cx| {
this.update(cx, |this, cx| this.render_section_button(ix, cx))
.unwrap_or_else(|_| div().into_any())
},
),
recent_projects: BTreeMap::default(),
vscode_settings: None,
active_step: 0,
}
});
this
}
fn render_subpane(
&mut self,
ix: usize,
window: &mut Window,
cx: &mut Context<Self>,
) -> AnyElement {
match &self.steps[ix] {
WalkthroughStep::Theme { tab_selection } => {
self.render_theme_step(tab_selection, window, cx)
}
WalkthroughStep::Settings { tab_selection } => {
self.render_settings_step(tab_selection, window, cx)
}
WalkthroughStep::AiIntegrations(state) => {
self.render_ai_integrations_step(state, window, cx)
}
WalkthroughStep::DataSharing => self.render_data_sharing_step(window, cx),
WalkthroughStep::OpenProject { tab_selection } => {
self.render_open_project_step(tab_selection, window, cx)
}
}
}
fn render_section_button(&mut self, ix: usize, cx: &mut Context<Self>) -> AnyElement {
match &self.steps[ix] {
WalkthroughStep::Theme { .. } => self.section_button(
ix,
"Pick a Theme",
"Select one of our built-in themes, or download one from the extensions page",
cx,
),
WalkthroughStep::Settings { .. } => self.section_button(
ix,
"Configure Zed",
"Set initial settings and/or import from other editors",
cx,
),
WalkthroughStep::AiIntegrations { .. } => self.section_button(
ix,
"AI Setup",
"Log in and pick providers for agentic editing and edit predictions",
cx,
),
WalkthroughStep::DataSharing => self.section_button(
ix,
"Data Sharing",
"Pick which data you send to the zed team",
cx,
),
WalkthroughStep::OpenProject { .. } => self.section_button(
ix,
"Open a Project",
"Pick a recent project you had open in another editor, or start something new",
cx,
),
}
}
fn render_data_sharing_step(
&self,
_window: &mut Window,
cx: &mut Context<Walkthrough>,
) -> AnyElement {
v_flex()
.items_center()
.justify_center()
.children({
let telemetry_settings = TelemetrySettings::get_global(cx);
[
CheckboxWithLabel::new(
"crashes",
Label::new("Send Crash Reports"),
telemetry_settings.diagnostics.into(),
{
let fs = self.fs.clone();
move |state, _, cx| {
let enabled = *state == ToggleState::Selected;
settings::update_settings_file::<TelemetrySettings>(
fs.clone(),
cx,
move |settings, _| settings.diagnostics = Some(enabled),
);
}
},
)
.into_any_element(),
CheckboxWithLabel::new(
"telemetry",
Label::new("Send Telemetry"),
telemetry_settings.metrics.into(),
{
let fs = self.fs.clone();
move |state, _, cx| {
let fs = fs.clone();
let enabled = *state == ToggleState::Selected;
settings::update_settings_file::<TelemetrySettings>(
fs.clone(),
cx,
move |settings, _| settings.metrics = Some(enabled),
);
}
},
)
.into_any_element(),
Divider::horizontal().into_any_element(),
CheckboxWithLabel::new(
"predictions",
Label::new("Help Improve Edit Predictions"),
false.into(),
|_, _, _| {}, // TODO
)
.into_any_element(),
CheckboxWithLabel::new(
"agent",
Label::new("Rate Agentic Edits"),
false.into(),
|_, _, _| {}, // TODO
)
.into_any_element(), // TODO: add note about how zed never shares your code/data by default
]
})
.into_any()
}
fn render_settings_step(
&self,
_tab_selection: &Entity<usize>,
_window: &mut Window,
cx: &mut Context<Walkthrough>,
) -> AnyElement {
let fs = self.fs.clone();
let vscode_settings_modified = self.vscode_settings;
v_flex()
.items_center()
.justify_center()
.size_full()
// TODO: header
.child("Pick a keymap")
.child(
h_flex().children(
{
use BaseKeymap::*;
[VSCode, Atom, SublimeText, JetBrains, TextMate, Emacs]
}
.into_iter()
.enumerate()
.map(|(i, name)| {
let fs = fs.clone();
Button::new(i, {
let s = name.to_string();
s.strip_suffix(" (beta)")
.map(ToOwned::to_owned)
.unwrap_or(s)
})
.on_click(move |_event, _window, cx| {
telemetry::event!(
"Settings Changed",
setting = "keymap",
value = &name
);
settings::update_settings_file::<BaseKeymap>(
fs.clone(),
cx,
move |settings, _| *settings = Some(name),
);
})
.toggle_state(name == *BaseKeymap::get_global(cx))
// TODO: styling from transparent_tabs and on-click from theme previews
}),
),
)
.child(CheckboxWithLabel::new(
"vim-mode",
Label::new("Vim mode?"),
VimModeSetting::get_global(cx).0.into(),
move |state, _, cx| {
let fs = fs.clone();
let enabled = *state == ToggleState::Selected;
telemetry::event!("Settings Changed", setting = "vim mode", value = enabled);
settings::update_settings_file::<VimModeSetting>(
fs.clone(),
cx,
move |settings, _| *settings = Some(enabled),
);
},
))
// TODO: gap
.child(
Button::new("extensions", "Browse extensions").on_click(|_, window, cx| {
window.dispatch_action(
Box::new(Extensions {
category_filter: None,
}),
cx,
)
}),
)
.when(cfg!(macos), |this| {
this.child(
h_flex()
.child(Button::new("install-cli", "Install cli"))
// TODO: install on-click
.child("Install a `zed` binary that\ncan be run from the command line"),
)
})
.when_some(vscode_settings_modified, |this, mtime| {
this.child(
h_flex()
.child(Button::new("import-vscode", "Import VsCode settings"))
.child(
Label::new(format!(
"(last modified {})",
time_format::format_local_timestamp(
mtime.into(),
OffsetDateTime::now_utc(),
TimestampFormat::Relative
),
))
.size(LabelSize::XSmall)
.color(Color::Muted),
),
)
})
// TODO: pad to bottom
.child(h_flex().children([
// TODO: on click action dispatchers
Button::new("open-settings", "open settings").on_click(cx.listener(
|this, _, window, cx| {
this.workspace
.update(cx, |_workspace, cx| {
window.dispatch_action(Box::new(OpenSettings), cx)
})
.ok();
},
)),
Button::new("open-keymap", "open keymap").on_click(cx.listener(
|this, _, window, cx| {
this.workspace
.update(cx, |_workspace, cx| {
window.dispatch_action(Box::new(OpenKeymap), cx)
})
.ok();
},
)),
Button::new("open-settings-docs", "open config docs").on_click(|_, _window, cx| {
cx.open_url("https://zed.dev/docs/configuring-zed");
}),
]))
.into_any()
}
fn render_theme_step(
&self,
theme_tab_selection: &Entity<usize>,
_window: &mut Window,
cx: &mut Context<Walkthrough>,
) -> AnyElement {
let fs = self.fs.clone();
v_flex()
.size_full()
.child(
PillTabs::new(theme_tab_selection.clone())
.tab("Dark", {
let fs = fs.clone();
move |window, cx| {
v_flex().children(
[
theme_preview_tile("One Dark", &fs, window, cx),
theme_preview_tile("Ayu Dark", &fs, window, cx),
theme_preview_tile("Gruvbox Dark", &fs, window, cx),
]
.into_iter()
.flatten(),
)
}
})
.tab("Light", {
let fs = fs.clone();
move |window, cx| {
v_flex().children(
[
theme_preview_tile("One Light", &fs, window, cx),
theme_preview_tile("Ayu Light", &fs, window, cx),
theme_preview_tile("Gruvbox Light", &fs, window, cx),
]
.into_iter()
.flatten(),
)
}
})
// TODO: picking a theme in the system tab should set both your light and dark themes
.tab("System", {
let fs = fs.clone();
move |window, cx| {
let current = match window.appearance() {
gpui::WindowAppearance::Light
| gpui::WindowAppearance::VibrantLight => "Light",
gpui::WindowAppearance::Dark
| gpui::WindowAppearance::VibrantDark => "Dark",
};
v_flex().children(
[
theme_preview_tile(&format!("One {current}"), &fs, window, cx),
theme_preview_tile(&format!("Ayu {current}"), &fs, window, cx),
theme_preview_tile(
&format!("Gruvbox {current}"),
&fs,
window,
cx,
),
]
.into_iter()
.flatten(),
)
}
}),
)
.child(
h_flex().justify_between().children([Button::new(
"install-theme",
"Browse More Themes",
)
.icon(IconName::SwatchBook)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(cx.listener(|this, _, window, cx| {
telemetry::event!("Welcome Theme Changed");
this.workspace
.update(cx, |_workspace, cx| {
window.dispatch_action(
Box::new(Extensions {
category_filter: Some(ExtensionCategoryFilter::Themes),
}),
cx,
);
})
.ok();
}))]),
)
.into_any()
}
fn render_ai_integrations_step(
&self,
state: &AiIntegrationState,
_window: &mut Window,
_cx: &mut Context<Walkthrough>,
) -> AnyElement {
let mut tabs = PillTabs::new(state.tab_selection.clone());
for (name, view) in &state.model_providers {
tabs = tabs.tab(name.0.as_ref(), {
let scroll_handle = state.scroll_handle.clone();
let scrollbar_state = state.scrollbar_state.clone();
let view = view.clone();
move |window, _cx| {
// div()
// .relative()
// .size_full()
// .p_1()
// .child(
div()
.id("provider-configuration")
.size_full()
.track_scroll(&scroll_handle)
.overflow_y_scroll()
.child(view.clone().into_any())
.child(scrollbar(scrollbar_state.clone(), window))
// )
}
});
}
tabs.into_any_element()
}
fn render_open_project_step(
&self,
tab_selection: &Entity<usize>,
_window: &mut Window,
_cx: &mut Context<Walkthrough>,
) -> AnyElement {
static HOME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(&format!("^{}", paths::home_dir().to_string_lossy())).unwrap()
});
let recents = if !self.recent_projects.is_empty() {
let mut tabs = PillTabs::new(tab_selection.clone());
for (name, projects) in &self.recent_projects {
let workspace = self.workspace.clone();
let projects = projects.clone();
tabs = tabs.tab(name.to_owned(), move |_window, _cx| {
let workspace = workspace.clone();
let projects = projects.clone();
v_flex().children(projects.into_iter().enumerate().map(move |(i, path)| {
Button::new(i, HOME_REGEX.replace(&path, "~").to_string()).on_click(
move |_, window, cx| {
let dir = PathBuf::from(&path);
let Some(app_state) = workspace::AppState::try_global(cx)
.and_then(|app_state| app_state.upgrade())
else {
return;
};
window
.spawn(cx, async move |cx| {
cx.update(|_window, cx| {
workspace::open_paths(
&[dir],
app_state,
workspace::OpenOptions::default(),
cx,
)
})
})
.detach();
},
)
}))
})
}
tabs.into_any_element()
} else {
"No Recent projects found".into_any()
};
v_flex().justify_between()
.child(recents)
.child(
h_flex()
.child(
Button::new("open-remote", "Open Remote").on_click(|_, window, cx| {
window.dispatch_action(Box::new(zed_actions::OpenRemote), cx)
}),
)
.child(
Button::new("open-new", "Open File").on_click(|_, window, cx| {
window.dispatch_action(Box::new(OpenFiles), cx)
}),
),
)
.into_any()
// TODO: add "open project", "connect to remote host", and "new file" buttons
}
}
fn theme_preview_tile(
name: &str,
fs: &Arc<dyn Fs>,
window: &mut Window,
cx: &mut App,
) -> Option<AnyElement> {
const THEME_PREVIEW_SEED: f32 = 0.42;
let theme_registry = ThemeRegistry::global(cx);
let theme = theme_registry.clone().get(name).ok()?;
let current_theme = cx.theme().clone();
let is_selected = current_theme.id == theme.id;
let fs = fs.clone();
Some(
v_flex()
.items_center()
.id(theme.name.clone())
.child(
div().w(px(200.)).h(px(120.)).child(
ThemePreviewTile::new(theme.clone(), is_selected, THEME_PREVIEW_SEED)
.render(window, cx)
.into_any_element(),
),
)
.text_ui_sm()
.child(theme.name.clone())
.on_click(move |_event, _window, cx| {
let name = theme.name.to_string();
telemetry::event!("Settings Changed", setting = "theme", value = &name);
settings::update_settings_file::<ThemeSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_static_theme(name);
},
);
})
.into_any(),
)
}
impl Render for Walkthrough {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.size_full()
.key_context("Walkthrough")
.bg(cx.theme().colors().editor_background)
.track_focus(&self.focus_handle(cx))
.p_5()
.child(
v_flex()
.size_full()
.items_center()
.justify_center()
.relative()
.child(
v_flex()
.child(
svg()
.path("icons/logo_96.svg")
.text_color(cx.theme().colors().icon_disabled)
.w(px(40.))
.h(px(40.))
.mx_auto()
.mb_4(),
)
.child(
h_flex()
.w_full()
.justify_center()
.child(Headline::new("Welcome to Zed")),
)
.child(
h_flex().w_full().justify_center().child(
Label::new("The editor for what's next")
.color(Color::Muted)
.italic(),
),
),
)
.child(
h_flex()
.w(px(576.0))
// .h_full()
.child(
list(self.list.clone())
.with_sizing_behavior(ListSizingBehavior::Infer)
.h_full()
.w(relative(0.33)),
)
.child(Divider::vertical())
.child(
div()
.w(relative(0.66))
.h_96()
.m_4()
.child(self.render_subpane(self.active_step, window, cx)),
),
),
)
}
}
impl EventEmitter<ItemEvent> for Walkthrough {}
impl Focusable for Walkthrough {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Item for Walkthrough {
type Event = ItemEvent;
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
"Walkthrough".into()
}
fn telemetry_event_text(&self) -> Option<&'static str> {
Some("Walkthrough Page Opened")
}
fn show_toolbar(&self) -> bool {
false
}
fn clone_on_split(
&self,
_workspace_id: Option<WorkspaceId>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Entity<Self>> {
self.workspace
.update(cx, |workspace, cx| Walkthrough::new(workspace, window, cx))
.ok()
}
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
f(*event)
}
}
impl SerializableItem for Walkthrough {
fn serialized_item_kind() -> &'static str {
"Walkthrough"
}
fn cleanup(
workspace_id: WorkspaceId,
alive_items: Vec<workspace::ItemId>,
_window: &mut Window,
cx: &mut App,
) -> gpui::Task<gpui::Result<()>> {
delete_unloaded_items(
alive_items,
workspace_id,
"walkthroughs",
&WALKTHROUGH_DB,
cx,
)
}
fn deserialize(
_project: Entity<project::Project>,
workspace: WeakEntity<Workspace>,
workspace_id: WorkspaceId,
item_id: workspace::ItemId,
window: &mut Window,
cx: &mut App,
) -> gpui::Task<gpui::Result<Entity<Self>>> {
let has_walkthrough = WALKTHROUGH_DB.get_walkthrough(item_id, workspace_id);
window.spawn(cx, async move |cx| {
has_walkthrough?;
workspace.update_in(cx, |workspace, window, cx| {
Walkthrough::new(workspace, window, cx)
})
})
}
fn serialize(
&mut self,
workspace: &mut Workspace,
item_id: workspace::ItemId,
_closing: bool,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<gpui::Task<gpui::Result<()>>> {
let workspace_id = workspace.database_id()?;
Some(cx.background_spawn(async move {
WALKTHROUGH_DB.save_walkthrough(item_id, workspace_id).await
}))
}
fn should_serialize(&self, _event: &Self::Event) -> bool {
false
}
}
mod persistence {
use db::{define_connection, query, sqlez_macros::sql};
use workspace::{ItemId, WorkspaceDb};
define_connection! {
pub static ref WALKTHROUGH_DB: WalkthroughDb<WorkspaceDb> =
&[sql!(
CREATE TABLE walkthroughs (
workspace_id INTEGER,
item_id INTEGER UNIQUE,
PRIMARY KEY(workspace_id, item_id),
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
ON DELETE CASCADE
) STRICT;
)];
}
impl WalkthroughDb {
query! {
pub async fn save_walkthrough(item_id: ItemId, workspace_id: workspace::WorkspaceId) -> Result<()> {
INSERT INTO walkthroughs(item_id, workspace_id)
VALUES (?1, ?2)
ON CONFLICT DO UPDATE SET
item_id = ?1,
workspace_id = ?2
}
}
query! {
pub fn get_walkthrough(item_id: ItemId, workspace_id: workspace::WorkspaceId) -> Result<ItemId> {
SELECT item_id
FROM walkthroughs
WHERE item_id = ? AND workspace_id = ?
}
}
}
}

View File

@@ -23,6 +23,8 @@ pub use multibuffer_hint::*;
mod base_keymap_picker;
mod base_keymap_setting;
mod multibuffer_hint;
mod recent_projects;
mod walkthrough;
mod welcome_ui;
actions!(welcome, [ResetHints]);
@@ -44,6 +46,7 @@ pub fn init(cx: &mut App) {
})
.detach();
walkthrough::init(cx);
base_keymap_picker::init(cx);
}

View File

@@ -1 +1,2 @@
mod theme_preview;
pub mod theme_preview;
pub mod pill_tabs;

View File

@@ -0,0 +1,106 @@
use std::sync::OnceLock;
use gpui::Entity;
use ui::{Divider, IntoElement, RenderOnce, component_prelude::Documented, prelude::*};
/// The tabs in the Zed walkthrough
#[derive(IntoElement, RegisterComponent, Documented)]
pub struct PillTabs {
selected: Entity<usize>,
tabs: Vec<Tab>,
}
struct Tab {
tab_title: String,
content: Option<Box<dyn Fn(&mut ui::Window, &mut ui::App) -> AnyElement>>,
}
impl PillTabs {
pub fn new(selected: Entity<usize> ) -> Self {
Self {
selected,
tabs: Vec::new(),
}
}
pub fn tab<R: IntoElement>(
mut self,
tab_title: &str,
content: impl Fn(&mut ui::Window, &mut ui::App) -> R + 'static,
) -> Self {
self.tabs.push(Tab {
tab_title: tab_title.to_owned(),
content: Some(Box::new(move |window, cx| {
content(window, cx).into_any_element()
})),
});
self
}
}
impl RenderOnce for PillTabs {
fn render(mut self, window: &mut ui::Window, cx: &mut ui::App) -> impl IntoElement {
let content = self.tabs[*self.selected.read(cx)].content.take().unwrap();
let selected = *self.selected.read(cx);
div()
.h_full()
.child(
h_flex()
.flex_wrap()
.children(self.tabs.into_iter().enumerate().map(|(i, t)| {
// using index was causing id collisions with the content from that tab...
// should probably do something more robust for that
Button::new(i + 100, t.tab_title)
.toggle_state(i == selected)
// .when(i==selected, this.bg(cx.theme().colors().element_selected))
.selected_style(ButtonStyle::Filled)
.on_click({
let selected = self.selected.clone();
move |_, _window, cx| {
selected.update(cx, |selected, cx| {
*selected = i;
cx.notify();
})
}
})
}))
.flex_grow()
.justify_center(),
)
.child(Divider::horizontal())
.child(
div()
.size_full()
.child((content)(window, cx)),
)
}
}
impl Component for PillTabs {
fn description() -> Option<&'static str> {
Some(Self::DOCS)
}
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
static SELECTED: OnceLock<Entity<usize>> = OnceLock::new();
let selected = SELECTED.get_or_init(|| cx.new(|_| 0)).clone();
let tabs = PillTabs::new(selected)
.tab("Tab 1", |_window, _cx| div().size_10().bg(gpui::red()))
.tab("Tab 2", |_window, _cx| div().size_10().bg(gpui::blue()))
.tab("Tab 3", |_window, _cx| div().size_10().bg(gpui::green()));
Some(
v_flex()
.gap_6()
.p_4()
.children({
vec![example_group(vec![single_example(
"Default",
div().child(tabs).into_any_element(),
)])]
})
.into_any_element(),
)
}
}

View File

@@ -1,4 +1,3 @@
#![allow(unused, dead_code)]
use gpui::{Hsla, Length};
use std::sync::Arc;
use theme::{Theme, ThemeRegistry};
@@ -24,7 +23,7 @@ impl ThemePreviewTile {
}
}
pub fn selected(mut self, selected: bool) -> Self {
pub fn _selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
@@ -173,6 +172,7 @@ impl RenderOnce for ThemePreviewTile {
div()
.size_full()
.overflow_hidden()
.rounded(root_radius)
.bg(color.editor_background)
.p_2()
.child(pseudo_code_skeleton(self.theme.clone(), self.seed)),
@@ -261,7 +261,7 @@ impl Component for ThemePreviewTile {
themes_to_preview
.iter()
.enumerate()
.map(|(i, theme)| {
.map(|(_i, theme)| {
div().w(px(200.)).h(px(140.)).child(ThemePreviewTile::new(
theme.clone(),
false,

View File

@@ -188,6 +188,7 @@ actions!(
ToggleZoom,
Unfollow,
Welcome,
Walkthrough,
RestoreBanner,
]
);

View File

@@ -250,7 +250,7 @@ impl Render for ZedPredictModal {
.border_color(border_color)
.rounded_sm()
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::XSmall.rems(cx))
.text_size(TextSize::XSmall.rems())
.text_color(text_color)
.child("tab")
.with_animation(