Compare commits
26 Commits
commit-vie
...
add-walkth
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8f5028323 | ||
|
|
7bbe256e92 | ||
|
|
f7ab9143e0 | ||
|
|
5bccbfb7e3 | ||
|
|
0d2d1a94ca | ||
|
|
3c98065f09 | ||
|
|
513e078a36 | ||
|
|
27f1ee9834 | ||
|
|
f2ae55503f | ||
|
|
0214bed452 | ||
|
|
f0070913d5 | ||
|
|
fde482f5df | ||
|
|
525030751e | ||
|
|
08043c1954 | ||
|
|
5d51380f61 | ||
|
|
331afce18c | ||
|
|
caf4127d19 | ||
|
|
78cc4bc6c1 | ||
|
|
37d8082f76 | ||
|
|
956c0750d4 | ||
|
|
03cb4361ce | ||
|
|
02c578d9ac | ||
|
|
4d62c7985b | ||
|
|
e95c6ff5bc | ||
|
|
92334bffc7 | ||
|
|
1ec87176d0 |
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)),
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.")
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -3382,7 +3382,7 @@ impl GitPanel {
|
||||
)
|
||||
})
|
||||
})
|
||||
.text_ui_sm(cx)
|
||||
.text_ui_sm()
|
||||
.mx_auto()
|
||||
.text_color(Color::Placeholder.color(cx)),
|
||||
)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -149,7 +149,7 @@ impl ProjectIndexDebugView {
|
||||
let chunk = &state.chunks[ix];
|
||||
|
||||
div()
|
||||
.text_ui(cx)
|
||||
.text_ui()
|
||||
.w_full()
|
||||
.font(buffer_font)
|
||||
.child(
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
}))
|
||||
});
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
126
crates/welcome/src/recent_projects.rs
Normal file
126
crates/welcome/src/recent_projects.rs
Normal 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
|
||||
900
crates/welcome/src/walkthrough.rs
Normal file
900
crates/welcome/src/walkthrough.rs
Normal 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 = ?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
mod theme_preview;
|
||||
pub mod theme_preview;
|
||||
pub mod pill_tabs;
|
||||
|
||||
106
crates/welcome/src/welcome_ui/pill_tabs.rs
Normal file
106
crates/welcome/src/welcome_ui/pill_tabs.rs
Normal 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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -188,6 +188,7 @@ actions!(
|
||||
ToggleZoom,
|
||||
Unfollow,
|
||||
Welcome,
|
||||
Walkthrough,
|
||||
RestoreBanner,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user