Compare commits
26 Commits
github-tok
...
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",
|
"db",
|
||||||
"documented",
|
"documented",
|
||||||
"editor",
|
"editor",
|
||||||
|
"fs",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
"gpui",
|
"gpui",
|
||||||
"install_cli",
|
"install_cli",
|
||||||
"language",
|
"language",
|
||||||
|
"language_model",
|
||||||
"linkme",
|
"linkme",
|
||||||
|
"paths",
|
||||||
"picker",
|
"picker",
|
||||||
"project",
|
"project",
|
||||||
|
"regex",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
|
"smol",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"theme",
|
"theme",
|
||||||
|
"time",
|
||||||
|
"time_format",
|
||||||
"ui",
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
"vim_mode_setting",
|
"vim_mode_setting",
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ use std::time::Duration;
|
|||||||
use text::ToPoint;
|
use text::ToPoint;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
Disclosure, IconButton, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
|
prelude::*, scrollbar, Disclosure, IconButton, KeyBinding, PopoverMenuHandle, ScrollbarState, TextSize, Tooltip
|
||||||
Tooltip, prelude::*,
|
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use util::markdown::MarkdownCodeBlock;
|
use util::markdown::MarkdownCodeBlock;
|
||||||
@@ -179,8 +178,8 @@ fn parse_markdown(
|
|||||||
pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||||
let theme_settings = ThemeSettings::get_global(cx);
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
let colors = cx.theme().colors();
|
let colors = cx.theme().colors();
|
||||||
let ui_font_size = TextSize::Default.rems(cx);
|
let ui_font_size = TextSize::Default.rems();
|
||||||
let buffer_font_size = TextSize::Small.rems(cx);
|
let buffer_font_size = TextSize::Small.rems();
|
||||||
let mut text_style = window.text_style();
|
let mut text_style = window.text_style();
|
||||||
|
|
||||||
text_style.refine(&TextStyleRefinement {
|
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 {
|
fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
|
||||||
let theme_settings = ThemeSettings::get_global(cx);
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
let colors = cx.theme().colors();
|
let colors = cx.theme().colors();
|
||||||
let ui_font_size = TextSize::Default.rems(cx);
|
let ui_font_size = TextSize::Default.rems();
|
||||||
let buffer_font_size = TextSize::Small.rems(cx);
|
let buffer_font_size = TextSize::Small.rems();
|
||||||
let mut text_style = window.text_style();
|
let mut text_style = window.text_style();
|
||||||
|
|
||||||
text_style.refine(&TextStyleRefinement {
|
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_family: Some(theme_settings.buffer_font.family.clone()),
|
||||||
font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
|
font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
|
||||||
font_features: Some(theme_settings.buffer_font.features.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()
|
..Default::default()
|
||||||
},
|
},
|
||||||
heading: StyleRefinement {
|
heading: StyleRefinement {
|
||||||
@@ -1698,7 +1697,7 @@ impl ActiveThread {
|
|||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let settings = ThemeSettings::get_global(cx);
|
let settings = ThemeSettings::get_global(cx);
|
||||||
let font_size = TextSize::Small
|
let font_size = TextSize::Small
|
||||||
.rems(cx)
|
.rems()
|
||||||
.to_pixels(settings.agent_font_size(cx));
|
.to_pixels(settings.agent_font_size(cx));
|
||||||
let line_height = font_size * 1.75;
|
let line_height = font_size * 1.75;
|
||||||
|
|
||||||
@@ -2284,7 +2283,7 @@ impl ActiveThread {
|
|||||||
let is_user_message = message_role == Role::User;
|
let is_user_message = message_role == Role::User;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.when(is_user_message, |this| this.text_xs())
|
.when(is_user_message, |this| this.text_xs())
|
||||||
.children(
|
.children(
|
||||||
@@ -2313,7 +2312,7 @@ impl ActiveThread {
|
|||||||
let theme_settings = ThemeSettings::get_global(cx);
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
|
|
||||||
let buffer_font = theme_settings.buffer_font.family.clone();
|
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 {
|
text_style.refine(&TextStyleRefinement {
|
||||||
font_family: Some(buffer_font),
|
font_family: Some(buffer_font),
|
||||||
@@ -2525,7 +2524,7 @@ impl ActiveThread {
|
|||||||
.id(("thinking-content", ix))
|
.id(("thinking-content", ix))
|
||||||
.max_h_20()
|
.max_h_20()
|
||||||
.track_scroll(scroll_handle)
|
.track_scroll(scroll_handle)
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(
|
.child(
|
||||||
MarkdownElement::new(
|
MarkdownElement::new(
|
||||||
@@ -2554,7 +2553,7 @@ impl ActiveThread {
|
|||||||
.id(("thinking-content", ix))
|
.id(("thinking-content", ix))
|
||||||
.h_full()
|
.h_full()
|
||||||
.bg(editor_bg)
|
.bg(editor_bg)
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.child(
|
.child(
|
||||||
MarkdownElement::new(
|
MarkdownElement::new(
|
||||||
markdown.clone(),
|
markdown.clone(),
|
||||||
@@ -2616,7 +2615,7 @@ impl ActiveThread {
|
|||||||
.pl_2p5()
|
.pl_2p5()
|
||||||
.border_l_1()
|
.border_l_1()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.when(is_open, |this| {
|
.when(is_open, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
MarkdownElement::new(
|
MarkdownElement::new(
|
||||||
@@ -2694,21 +2693,19 @@ impl ActiveThread {
|
|||||||
let rendered_tool_use = self.rendered_tool_uses.get(&tool_use.id).cloned();
|
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_container = || v_flex().p_2().gap_0p5();
|
||||||
|
|
||||||
let results_content = v_flex()
|
let results_content =
|
||||||
.gap_1()
|
v_flex()
|
||||||
.child(
|
.gap_1()
|
||||||
results_content_container()
|
.child(
|
||||||
.child(
|
results_content_container()
|
||||||
Label::new("Input")
|
.child(
|
||||||
.size(LabelSize::XSmall)
|
Label::new("Input")
|
||||||
.color(Color::Muted)
|
.size(LabelSize::XSmall)
|
||||||
.buffer_font(cx),
|
.color(Color::Muted)
|
||||||
)
|
.buffer_font(cx),
|
||||||
.child(
|
)
|
||||||
div()
|
.child(div().w_full().text_ui_sm().children(
|
||||||
.w_full()
|
rendered_tool_use.as_ref().map(|rendered| {
|
||||||
.text_ui_sm(cx)
|
|
||||||
.children(rendered_tool_use.as_ref().map(|rendered| {
|
|
||||||
MarkdownElement::new(
|
MarkdownElement::new(
|
||||||
rendered.input.clone(),
|
rendered.input.clone(),
|
||||||
tool_use_markdown_style(window, cx),
|
tool_use_markdown_style(window, cx),
|
||||||
@@ -2723,83 +2720,81 @@ impl ActiveThread {
|
|||||||
open_markdown_link(text, workspace.clone(), window, cx);
|
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(
|
.map(|container| match tool_use.status {
|
||||||
results_content_container()
|
ToolUseStatus::Finished(_) => container.child(
|
||||||
.border_t_1()
|
results_content_container()
|
||||||
.border_color(self.tool_card_border_color(cx))
|
.border_t_1()
|
||||||
.child(
|
.border_color(self.tool_card_border_color(cx))
|
||||||
h_flex()
|
.child(
|
||||||
.gap_1()
|
Label::new("Result")
|
||||||
.child(
|
.size(LabelSize::XSmall)
|
||||||
Icon::new(IconName::ArrowCircle)
|
.color(Color::Muted)
|
||||||
.size(IconSize::Small)
|
.buffer_font(cx),
|
||||||
.color(Color::Accent)
|
)
|
||||||
.with_animation(
|
.child(div().w_full().text_ui_sm().children(
|
||||||
"arrow-circle",
|
rendered_tool_use.as_ref().map(|rendered| {
|
||||||
Animation::new(Duration::from_secs(2)).repeat(),
|
MarkdownElement::new(
|
||||||
|icon, delta| {
|
rendered.output.clone(),
|
||||||
icon.transform(Transformation::rotate(percentage(
|
tool_use_markdown_style(window, cx),
|
||||||
delta,
|
)
|
||||||
)))
|
.code_block_renderer(markdown::CodeBlockRenderer::Default {
|
||||||
},
|
copy_button: false,
|
||||||
),
|
border: false,
|
||||||
)
|
})
|
||||||
.child(
|
.on_url_click({
|
||||||
Label::new("Running…")
|
let workspace = self.workspace.clone();
|
||||||
.size(LabelSize::XSmall)
|
move |text, window, cx| {
|
||||||
.color(Color::Muted)
|
open_markdown_link(text, workspace.clone(), window, cx);
|
||||||
.buffer_font(cx),
|
}
|
||||||
),
|
})
|
||||||
),
|
.into_any_element()
|
||||||
),
|
}),
|
||||||
ToolUseStatus::Error(_) => container.child(
|
)),
|
||||||
results_content_container()
|
),
|
||||||
.border_t_1()
|
ToolUseStatus::InputStillStreaming | ToolUseStatus::Running => container.child(
|
||||||
.border_color(self.tool_card_border_color(cx))
|
results_content_container()
|
||||||
.child(
|
.border_t_1()
|
||||||
Label::new("Error")
|
.border_color(self.tool_card_border_color(cx))
|
||||||
.size(LabelSize::XSmall)
|
.child(
|
||||||
.color(Color::Muted)
|
h_flex()
|
||||||
.buffer_font(cx),
|
.gap_1()
|
||||||
)
|
.child(
|
||||||
.child(
|
Icon::new(IconName::ArrowCircle)
|
||||||
div()
|
.size(IconSize::Small)
|
||||||
.text_ui_sm(cx)
|
.color(Color::Accent)
|
||||||
.children(rendered_tool_use.as_ref().map(|rendered| {
|
.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(
|
MarkdownElement::new(
|
||||||
rendered.output.clone(),
|
rendered.output.clone(),
|
||||||
tool_use_markdown_style(window, cx),
|
tool_use_markdown_style(window, cx),
|
||||||
@@ -2811,22 +2806,22 @@ impl ActiveThread {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
})),
|
},
|
||||||
),
|
))),
|
||||||
),
|
),
|
||||||
ToolUseStatus::Pending => container,
|
ToolUseStatus::Pending => container,
|
||||||
ToolUseStatus::NeedsConfirmation => container.child(
|
ToolUseStatus::NeedsConfirmation => container.child(
|
||||||
results_content_container()
|
results_content_container()
|
||||||
.border_t_1()
|
.border_t_1()
|
||||||
.border_color(self.tool_card_border_color(cx))
|
.border_color(self.tool_card_border_color(cx))
|
||||||
.child(
|
.child(
|
||||||
Label::new("Asking Permission")
|
Label::new("Asking Permission")
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.buffer_font(cx),
|
.buffer_font(cx),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
let gradient_overlay = |color: Hsla| {
|
let gradient_overlay = |color: Hsla| {
|
||||||
div()
|
div()
|
||||||
@@ -2964,7 +2959,7 @@ impl ActiveThread {
|
|||||||
.color(Color::Muted),
|
.color(Color::Muted),
|
||||||
)
|
)
|
||||||
.child(
|
.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| {
|
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);
|
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() {
|
if !self.show_scrollbar && !self.scrollbar_state.is_dragging() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(
|
Some(scrollbar(self.scrollbar_state.clone(), window))
|
||||||
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())),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_scrollbar_later(&mut self, cx: &mut Context<Self>) {
|
fn hide_scrollbar_later(&mut self, cx: &mut Context<Self>) {
|
||||||
@@ -3390,7 +3354,7 @@ pub enum ActiveThreadEvent {
|
|||||||
impl EventEmitter<ActiveThreadEvent> for ActiveThread {}
|
impl EventEmitter<ActiveThreadEvent> for ActiveThread {}
|
||||||
|
|
||||||
impl Render 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()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.relative()
|
.relative()
|
||||||
@@ -3411,7 +3375,7 @@ impl Render for ActiveThread {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(list(self.list_state.clone()).flex_grow())
|
.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)
|
this.child(scrollbar)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageMod
|
|||||||
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
||||||
use settings::{Settings, update_settings_file};
|
use settings::{Settings, update_settings_file};
|
||||||
use ui::{
|
use ui::{
|
||||||
Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Scrollbar, ScrollbarState,
|
prelude::*, scrollbar, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip
|
||||||
Switch, SwitchColor, Tooltip, prelude::*,
|
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use zed_actions::ExtensionCategoryFilter;
|
use zed_actions::ExtensionCategoryFilter;
|
||||||
@@ -593,31 +592,6 @@ impl Render for AgentConfiguration {
|
|||||||
.child(Divider::horizontal().color(DividerColor::Border))
|
.child(Divider::horizontal().color(DividerColor::Border))
|
||||||
.child(self.render_provider_configuration_section(cx)),
|
.child(self.render_provider_configuration_section(cx)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(scrollbar(self.scrollbar_state.clone(), window))
|
||||||
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())),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_family: Some(theme_settings.ui_font.family.clone()),
|
||||||
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
||||||
font_features: Some(theme_settings.ui_font.features.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),
|
color: Some(colors.text_muted),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -759,7 +759,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
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;
|
let line_height = font_size.to_pixels(window.rem_size()) * 1.3;
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
|||||||
@@ -629,7 +629,7 @@ impl MessageEditor {
|
|||||||
.child({
|
.child({
|
||||||
let settings = ThemeSettings::get_global(cx);
|
let settings = ThemeSettings::get_global(cx);
|
||||||
let font_size = TextSize::Small
|
let font_size = TextSize::Small
|
||||||
.rems(cx)
|
.rems()
|
||||||
.to_pixels(settings.agent_font_size(cx));
|
.to_pixels(settings.agent_font_size(cx));
|
||||||
let line_height = settings.buffer_line_height.value() * font_size;
|
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 action_log = self.thread.read(cx).action_log();
|
||||||
let changed_buffers = action_log.read(cx).changed_buffers(cx);
|
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()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use gpui::{
|
|||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use ui::{
|
use ui::{
|
||||||
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
|
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
|
||||||
Tooltip, prelude::*,
|
Tooltip, prelude::*, scrollbar,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
@@ -340,40 +340,21 @@ impl ThreadHistory {
|
|||||||
cx.notify();
|
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()) {
|
if !(self.scrollbar_visibility || self.scrollbar_state.is_dragging()) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
div()
|
scrollbar(self.scrollbar_state.clone(), window)
|
||||||
.occlude()
|
|
||||||
.id("thread-history-scroll")
|
|
||||||
.h_full()
|
|
||||||
.bg(cx.theme().colors().panel_background.opacity(0.8))
|
.bg(cx.theme().colors().panel_background.opacity(0.8))
|
||||||
.border_l_1()
|
.border_l_1()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.absolute()
|
.pl_1(),
|
||||||
.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())),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,7 +510,7 @@ impl Focusable for ThreadHistory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render 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()
|
v_flex()
|
||||||
.key_context("ThreadHistory")
|
.key_context("ThreadHistory")
|
||||||
.size_full()
|
.size_full()
|
||||||
@@ -592,7 +573,7 @@ impl Render for ThreadHistory {
|
|||||||
.track_scroll(self.scroll_handle.clone())
|
.track_scroll(self.scroll_handle.clone())
|
||||||
.flex_grow(),
|
.flex_grow(),
|
||||||
)
|
)
|
||||||
.when_some(self.render_scrollbar(cx), |div, scrollbar| {
|
.when_some(self.render_scrollbar(window, cx), |div, scrollbar| {
|
||||||
div.child(scrollbar)
|
div.child(scrollbar)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ impl Render for AgentNotification {
|
|||||||
.gap_4()
|
.gap_4()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.elevation_3(cx)
|
.elevation_3(cx)
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.font(ui_font)
|
.font(ui_font)
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.rounded_xl()
|
.rounded_xl()
|
||||||
|
|||||||
@@ -1367,7 +1367,7 @@ impl ContextEditor {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
.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)
|
.text_color(colors.text_muted)
|
||||||
.child("Press")
|
.child("Press")
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
@@ -577,7 +577,7 @@ impl ToolCard for EditFileToolCard {
|
|||||||
editor.set_text_style_refinement(TextStyleRefinement {
|
editor.set_text_style_refinement(TextStyleRefinement {
|
||||||
font_size: Some(
|
font_size: Some(
|
||||||
TextSize::Small
|
TextSize::Small
|
||||||
.rems(cx)
|
.rems()
|
||||||
.to_pixels(ThemeSettings::get_global(cx).agent_font_size(cx))
|
.to_pixels(ThemeSettings::get_global(cx).agent_font_size(cx))
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
@@ -682,7 +682,7 @@ impl ToolCard for EditFileToolCard {
|
|||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.children(
|
.children(
|
||||||
error_message
|
error_message
|
||||||
|
|||||||
@@ -586,7 +586,7 @@ impl ToolCard for TerminalToolCard {
|
|||||||
.border_color(border_color)
|
.border_color(border_color)
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.rounded_b_md()
|
.rounded_b_md()
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.child(terminal.clone()),
|
.child(terminal.clone()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ impl Render for Breadcrumbs {
|
|||||||
.id("breadcrumb-container")
|
.id("breadcrumb-container")
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.overflow_x_scroll()
|
.overflow_x_scroll()
|
||||||
.text_ui(cx);
|
.text_ui();
|
||||||
|
|
||||||
let Some(active_item) = self.active_item.as_ref() else {
|
let Some(active_item) = self.active_item.as_ref() else {
|
||||||
return element;
|
return element;
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ impl ChatPanel {
|
|||||||
None => {
|
None => {
|
||||||
return div().child(
|
return div().child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.text_ui_xs(cx)
|
.text_ui_xs()
|
||||||
.my_0p5()
|
.my_0p5()
|
||||||
.px_0p5()
|
.px_0p5()
|
||||||
.gap_x_1()
|
.gap_x_1()
|
||||||
@@ -354,7 +354,7 @@ impl ChatPanel {
|
|||||||
div().child(
|
div().child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(message_element_id)
|
.id(message_element_id)
|
||||||
.text_ui_xs(cx)
|
.text_ui_xs()
|
||||||
.my_0p5()
|
.my_0p5()
|
||||||
.px_0p5()
|
.px_0p5()
|
||||||
.gap_x_1()
|
.gap_x_1()
|
||||||
@@ -504,7 +504,7 @@ impl ChatPanel {
|
|||||||
this.child(
|
this.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.child(
|
.child(
|
||||||
Avatar::new(message.sender.avatar_uri.clone())
|
Avatar::new(message.sender.avatar_uri.clone())
|
||||||
.size(rems(1.)),
|
.size(rems(1.)),
|
||||||
@@ -541,7 +541,7 @@ impl ChatPanel {
|
|||||||
el.child(
|
el.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.id(element_id)
|
.id(element_id)
|
||||||
.child(text.element("body".into(), window, cx)),
|
.child(text.element("body".into(), window, cx)),
|
||||||
)
|
)
|
||||||
@@ -564,7 +564,7 @@ impl ChatPanel {
|
|||||||
div()
|
div()
|
||||||
.px_1()
|
.px_1()
|
||||||
.rounded_sm()
|
.rounded_sm()
|
||||||
.text_ui_xs(cx)
|
.text_ui_xs()
|
||||||
.bg(cx.theme().colors().background)
|
.bg(cx.theme().colors().background)
|
||||||
.child("New messages"),
|
.child("New messages"),
|
||||||
)
|
)
|
||||||
@@ -1011,7 +1011,7 @@ impl Render for ChatPanel {
|
|||||||
el.child(
|
el.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.px_2()
|
.px_2()
|
||||||
.text_ui_xs(cx)
|
.text_ui_xs()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.border_t_1()
|
.border_t_1()
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ impl Render for MessageEditor {
|
|||||||
font_family: settings.ui_font.family.clone(),
|
font_family: settings.ui_font.family.clone(),
|
||||||
font_features: settings.ui_font.features.clone(),
|
font_features: settings.ui_font.features.clone(),
|
||||||
font_fallbacks: settings.ui_font.fallbacks.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_weight: settings.ui_font.weight,
|
||||||
font_style: FontStyle::Normal,
|
font_style: FontStyle::Normal,
|
||||||
line_height: relative(1.3),
|
line_height: relative(1.3),
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ impl ParentElement for CollabNotification {
|
|||||||
impl RenderOnce for CollabNotification {
|
impl RenderOnce for CollabNotification {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.size_full()
|
.size_full()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
|
|||||||
@@ -629,7 +629,7 @@ impl ComponentPreview {
|
|||||||
.when_some(description, |this, description| {
|
.when_some(description, |this, description| {
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.text_color(cx.theme().colors().text_muted)
|
.text_color(cx.theme().colors().text_muted)
|
||||||
.max_w(px(600.0))
|
.max_w(px(600.0))
|
||||||
.child(description),
|
.child(description),
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ use project::{
|
|||||||
use ui::{
|
use ui::{
|
||||||
App, Clickable, Color, Context, Div, Icon, IconButton, IconName, Indicator, InteractiveElement,
|
App, Clickable, Color, Context, Div, Icon, IconButton, IconName, Indicator, InteractiveElement,
|
||||||
IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement, Render, RenderOnce,
|
IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement, Render, RenderOnce,
|
||||||
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Window, div,
|
ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Window, div, h_flex, px,
|
||||||
h_flex, px, v_flex,
|
scrollbar, v_flex,
|
||||||
};
|
};
|
||||||
use util::{ResultExt, maybe};
|
use util::{ResultExt, maybe};
|
||||||
use workspace::Workspace;
|
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()) {
|
if !(self.show_scrollbar || self.scrollbar_state.is_dragging()) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(
|
Some(scrollbar(self.scrollbar_state.clone(), window))
|
||||||
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())),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Render for BreakpointList {
|
impl Render for BreakpointList {
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
_window: &mut ui::Window,
|
window: &mut ui::Window,
|
||||||
cx: &mut ui::Context<Self>,
|
cx: &mut ui::Context<Self>,
|
||||||
) -> impl ui::IntoElement {
|
) -> impl ui::IntoElement {
|
||||||
let old_len = self.breakpoints.len();
|
let old_len = self.breakpoints.len();
|
||||||
@@ -227,7 +196,7 @@ impl Render for BreakpointList {
|
|||||||
.size_full()
|
.size_full()
|
||||||
.m_0p5()
|
.m_0p5()
|
||||||
.child(list(self.list_state.clone()).flex_grow())
|
.child(list(self.list_state.clone()).flex_grow())
|
||||||
.children(self.render_vertical_scrollbar(cx))
|
.children(self.render_vertical_scrollbar(window))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|||||||
@@ -64,12 +64,12 @@ impl LoadedSourceList {
|
|||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.when_some(source.name.clone(), |this, name| this.child(name)),
|
.when_some(source.name.clone(), |this, name| this.child(name)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.text_ui_xs(cx)
|
.text_ui_xs()
|
||||||
.text_color(cx.theme().colors().text_muted)
|
.text_color(cx.theme().colors().text_muted)
|
||||||
.when_some(source.path.clone(), |this, path| this.child(path)),
|
.when_some(source.path.clone(), |this, path| this.child(path)),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, Empty, Entity, FocusHandle, Focusable, ListState, MouseButton, Stateful,
|
AnyElement, Empty, Entity, FocusHandle, Focusable, ListState, Subscription, WeakEntity, list,
|
||||||
Subscription, WeakEntity, list,
|
|
||||||
};
|
};
|
||||||
use project::{
|
use project::{
|
||||||
ProjectItem as _, ProjectPath,
|
ProjectItem as _, ProjectPath,
|
||||||
debugger::session::{Session, SessionEvent},
|
debugger::session::{Session, SessionEvent},
|
||||||
};
|
};
|
||||||
use std::{path::Path, sync::Arc};
|
use std::{path::Path, sync::Arc};
|
||||||
use ui::{Scrollbar, ScrollbarState, prelude::*};
|
use ui::{ScrollbarState, prelude::*, scrollbar};
|
||||||
use util::maybe;
|
use util::maybe;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -141,10 +140,10 @@ impl ModuleList {
|
|||||||
})
|
})
|
||||||
.p_1()
|
.p_1()
|
||||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
.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(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.text_ui_xs(cx)
|
.text_ui_xs()
|
||||||
.text_color(cx.theme().colors().text_muted)
|
.text_color(cx.theme().colors().text_muted)
|
||||||
.when_some(module.path.clone(), |this, path| this.child(path)),
|
.when_some(module.path.clone(), |this, path| this.child(path)),
|
||||||
)
|
)
|
||||||
@@ -156,38 +155,6 @@ impl ModuleList {
|
|||||||
self.session
|
self.session
|
||||||
.update(cx, |session, cx| session.modules(cx).to_vec())
|
.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 {
|
impl Focusable for ModuleList {
|
||||||
@@ -197,7 +164,7 @@ impl Focusable for ModuleList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render 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 {
|
if self.invalidate {
|
||||||
let len = self
|
let len = self
|
||||||
.session
|
.session
|
||||||
@@ -212,6 +179,6 @@ impl Render for ModuleList {
|
|||||||
.size_full()
|
.size_full()
|
||||||
.p_1()
|
.p_1()
|
||||||
.child(list(self.list.clone()).size_full())
|
.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 anyhow::{Result, anyhow};
|
||||||
use dap::StackFrameId;
|
use dap::StackFrameId;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, MouseButton, Stateful,
|
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, Subscription, Task,
|
||||||
Subscription, Task, WeakEntity, list,
|
WeakEntity, list,
|
||||||
};
|
};
|
||||||
|
|
||||||
use language::PointUtf16;
|
use language::PointUtf16;
|
||||||
use project::debugger::breakpoint_store::ActiveStackFrame;
|
use project::debugger::breakpoint_store::ActiveStackFrame;
|
||||||
use project::debugger::session::{Session, SessionEvent, StackFrame};
|
use project::debugger::session::{Session, SessionEvent, StackFrame};
|
||||||
use project::{ProjectItem, ProjectPath};
|
use project::{ProjectItem, ProjectPath};
|
||||||
use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*};
|
use ui::{ScrollbarState, Tooltip, prelude::*, scrollbar};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -488,7 +488,7 @@ impl StackFrameList {
|
|||||||
.hover(|style| style.bg(cx.theme().colors().element_hover).cursor_pointer())
|
.hover(|style| style.bg(cx.theme().colors().element_hover).cursor_pointer())
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.truncate()
|
.truncate()
|
||||||
.text_color(cx.theme().colors().text_muted)
|
.text_color(cx.theme().colors().text_muted)
|
||||||
.child(format!(
|
.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 {
|
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()
|
div()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.size_full()
|
.size_full()
|
||||||
.p_1()
|
.p_1()
|
||||||
.child(list(self.list.clone()).size_full())
|
.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 editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, ClickEvent, ClipboardItem, Context, DismissEvent, Entity, FocusHandle, Focusable,
|
AnyElement, ClickEvent, ClipboardItem, Context, DismissEvent, Entity, FocusHandle, Focusable,
|
||||||
Hsla, MouseButton, MouseDownEvent, Point, Stateful, Subscription, TextStyleRefinement,
|
Hsla, MouseDownEvent, Point, Subscription, TextStyleRefinement, UniformListScrollHandle,
|
||||||
UniformListScrollHandle, actions, anchored, deferred, uniform_list,
|
actions, anchored, deferred, uniform_list,
|
||||||
};
|
};
|
||||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};
|
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};
|
||||||
use project::debugger::session::{Session, SessionEvent};
|
use project::debugger::session::{Session, SessionEvent};
|
||||||
use std::{collections::HashMap, ops::Range, sync::Arc};
|
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};
|
use util::{debug_panic, maybe};
|
||||||
|
|
||||||
actions!(variable_list, [ExpandSelectedEntry, CollapseSelectedEntry]);
|
actions!(variable_list, [ExpandSelectedEntry, CollapseSelectedEntry]);
|
||||||
@@ -625,12 +625,7 @@ impl VariableList {
|
|||||||
let mut editor = Editor::single_line(window, cx);
|
let mut editor = Editor::single_line(window, cx);
|
||||||
|
|
||||||
let refinement = TextStyleRefinement {
|
let refinement = TextStyleRefinement {
|
||||||
font_size: Some(
|
font_size: Some(TextSize::XSmall.rems().to_pixels(window.rem_size()).into()),
|
||||||
TextSize::XSmall
|
|
||||||
.rems(cx)
|
|
||||||
.to_pixels(window.rem_size())
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
editor.set_text_style_refinement(refinement);
|
editor.set_text_style_refinement(refinement);
|
||||||
@@ -703,7 +698,7 @@ impl VariableList {
|
|||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.w_full()
|
.w_full()
|
||||||
.when(self.disabled, |this| {
|
.when(self.disabled, |this| {
|
||||||
this.text_color(Color::Disabled.color(cx))
|
this.text_color(Color::Disabled.color(cx))
|
||||||
@@ -822,7 +817,7 @@ impl VariableList {
|
|||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.w_full()
|
.w_full()
|
||||||
.child(
|
.child(
|
||||||
Label::new(&dap.name).when_some(variable_name_color, |this, color| {
|
Label::new(&dap.name).when_some(variable_name_color, |this, color| {
|
||||||
@@ -885,39 +880,6 @@ impl VariableList {
|
|||||||
)
|
)
|
||||||
.into_any()
|
.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 {
|
impl Focusable for VariableList {
|
||||||
@@ -927,7 +889,7 @@ impl Focusable for VariableList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render 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);
|
self.build_entries(cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -967,7 +929,7 @@ impl Render for VariableList {
|
|||||||
)
|
)
|
||||||
.with_priority(1)
|
.with_priority(1)
|
||||||
}))
|
}))
|
||||||
.child(self.render_vertical_scrollbar(cx))
|
.child(scrollbar(self.scrollbar_state.clone(), window))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7850,7 +7850,7 @@ impl Editor {
|
|||||||
.px_0p5()
|
.px_0p5()
|
||||||
.when(is_platform_style_mac, |parent| parent.gap_0p5())
|
.when(is_platform_style_mac, |parent| parent.gap_0p5())
|
||||||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
.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(
|
.child(h_flex().children(ui::render_modifiers(
|
||||||
&accept_keystroke.modifiers,
|
&accept_keystroke.modifiers,
|
||||||
PlatformStyle::platform(),
|
PlatformStyle::platform(),
|
||||||
@@ -21243,7 +21243,7 @@ struct MissingEditPredictionKeybindingTooltip;
|
|||||||
|
|
||||||
impl Render for MissingEditPredictionKeybindingTooltip {
|
impl Render for MissingEditPredictionKeybindingTooltip {
|
||||||
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 {
|
||||||
ui::tooltip_container(window, cx, |container, _, cx| {
|
ui::tooltip_container(window, cx, |container, _, _cx| {
|
||||||
container
|
container
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.max_w_80()
|
.max_w_80()
|
||||||
@@ -21252,7 +21252,7 @@ impl Render for MissingEditPredictionKeybindingTooltip {
|
|||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.child(Label::new("Conflict with Accept Keybinding"))
|
.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.")
|
.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 (normal_em, small_em) = {
|
||||||
let style = window.text_style();
|
let style = window.text_style();
|
||||||
let font_id = window.text_system().resolve_font(&style.font());
|
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
|
let normal = cx
|
||||||
.text_system()
|
.text_system()
|
||||||
.em_width(font_id, font_size)
|
.em_width(font_id, font_size)
|
||||||
.unwrap_or(px(16.));
|
.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
|
let small = cx
|
||||||
.text_system()
|
.text_system()
|
||||||
.em_width(font_id, font_size)
|
.em_width(font_id, font_size)
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ impl BlameRenderer for GitBlameRenderer {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.elevation_2(cx)
|
.elevation_2(cx)
|
||||||
.font(ui_font)
|
.font(ui_font)
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.text_color(cx.theme().colors().text)
|
.text_color(cx.theme().colors().text)
|
||||||
.py_1()
|
.py_1()
|
||||||
.px_2()
|
.px_2()
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ fn render_conflict_buttons(
|
|||||||
.px_1()
|
.px_1()
|
||||||
.child("Take Ours")
|
.child("Take Ours")
|
||||||
.rounded_t(rems(0.2))
|
.rounded_t(rems(0.2))
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.hover(|this| this.bg(cx.theme().colors().element_background))
|
.hover(|this| this.bg(cx.theme().colors().element_background))
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.on_click({
|
.on_click({
|
||||||
@@ -415,7 +415,7 @@ fn render_conflict_buttons(
|
|||||||
.px_1()
|
.px_1()
|
||||||
.child("Take Theirs")
|
.child("Take Theirs")
|
||||||
.rounded_t(rems(0.2))
|
.rounded_t(rems(0.2))
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.hover(|this| this.bg(cx.theme().colors().element_background))
|
.hover(|this| this.bg(cx.theme().colors().element_background))
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.on_click({
|
.on_click({
|
||||||
@@ -439,7 +439,7 @@ fn render_conflict_buttons(
|
|||||||
.px_1()
|
.px_1()
|
||||||
.child("Take Both")
|
.child("Take Both")
|
||||||
.rounded_t(rems(0.2))
|
.rounded_t(rems(0.2))
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.hover(|this| this.bg(cx.theme().colors().element_background))
|
.hover(|this| this.bg(cx.theme().colors().element_background))
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.on_click({
|
.on_click({
|
||||||
|
|||||||
@@ -3382,7 +3382,7 @@ impl GitPanel {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.mx_auto()
|
.mx_auto()
|
||||||
.text_color(Color::Placeholder.color(cx)),
|
.text_color(Color::Placeholder.color(cx)),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4071,6 +4071,8 @@ pub enum ElementId {
|
|||||||
NamedInteger(SharedString, u64),
|
NamedInteger(SharedString, u64),
|
||||||
/// A path
|
/// A path
|
||||||
Path(Arc<std::path::Path>),
|
Path(Arc<std::path::Path>),
|
||||||
|
/// A source location
|
||||||
|
Location(core::panic::Location<'static>)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementId {
|
impl ElementId {
|
||||||
@@ -4090,6 +4092,7 @@ impl Display for ElementId {
|
|||||||
ElementId::NamedInteger(s, i) => write!(f, "{}-{}", s, i)?,
|
ElementId::NamedInteger(s, i) => write!(f, "{}-{}", s, i)?,
|
||||||
ElementId::Uuid(uuid) => write!(f, "{}", uuid)?,
|
ElementId::Uuid(uuid) => write!(f, "{}", uuid)?,
|
||||||
ElementId::Path(path) => write!(f, "{}", path.display())?,
|
ElementId::Path(path) => write!(f, "{}", path.display())?,
|
||||||
|
ElementId::Location(location) => write!(f, "{}:{}:{}", location.file(), location.line(), location.column())?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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 {
|
impl From<(&'static str, u32)> for ElementId {
|
||||||
fn from((name, id): (&'static str, u32)) -> Self {
|
fn from((name, id): (&'static str, u32)) -> Self {
|
||||||
ElementId::NamedInteger(name.into(), id.into())
|
ElementId::NamedInteger(name.into(), id.into())
|
||||||
|
|||||||
@@ -951,7 +951,7 @@ impl ConfigurationView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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;
|
let env_var_set = self.state.read(cx).api_key_from_env;
|
||||||
|
|
||||||
if self.load_credentials_task.is_some() {
|
if self.load_credentials_task.is_some() {
|
||||||
|
|||||||
@@ -1199,7 +1199,7 @@ impl ConfigurationView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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 env_var_set = self.state.read(cx).credentials_from_env;
|
||||||
let creds_type = self.should_render_editor(cx).is_some();
|
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::*};
|
use ui::{ListItem, prelude::*};
|
||||||
|
|
||||||
/// A reusable list item component for adding LLM provider configuration instructions
|
/// 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)
|
(self.button_label, self.button_link)
|
||||||
{
|
{
|
||||||
let link = button_link.clone();
|
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)
|
Button::new("link-button", button_label)
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.icon(IconName::ArrowUpRight)
|
.icon(IconName::ArrowUpRight)
|
||||||
@@ -47,7 +47,7 @@ impl IntoElement for InstructionListItem {
|
|||||||
.on_click(move |_, _window, cx| cx.open_url(&link)),
|
.on_click(move |_, _window, cx| cx.open_url(&link)),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
div().child(Label::new(self.label))
|
div().w_full().text_ui_sm().overflow_x_hidden().child(self.label)
|
||||||
};
|
};
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ impl PickerDelegate for OutlineViewDelegate {
|
|||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.pl(rems(outline_item.depth as f32))
|
.pl(rems(outline_item.depth as f32))
|
||||||
.child(render_item(outline_item, mat.ranges(), cx)),
|
.child(render_item(outline_item, mat.ranges(), cx)),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2456,7 +2456,7 @@ impl OutlinePanel {
|
|||||||
) -> Stateful<Div> {
|
) -> Stateful<Div> {
|
||||||
let settings = OutlinePanelSettings::get_global(cx);
|
let settings = OutlinePanelSettings::get_global(cx);
|
||||||
div()
|
div()
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.id(item_id.clone())
|
.id(item_id.clone())
|
||||||
.on_click({
|
.on_click({
|
||||||
let clicked_entry = rendered_entry.clone();
|
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 {
|
pub fn panel_editor_style(monospace: bool, window: &Window, cx: &App) -> EditorStyle {
|
||||||
let settings = ThemeSettings::get_global(cx);
|
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 {
|
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_family,
|
||||||
font_fallbacks,
|
font_fallbacks,
|
||||||
font_features,
|
font_features,
|
||||||
font_size: TextSize::Small.rems(cx).into(),
|
font_size: TextSize::Small.rems().into(),
|
||||||
font_weight,
|
font_weight,
|
||||||
line_height: line_height.into(),
|
line_height: line_height.into(),
|
||||||
..Default::default()
|
..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
|
/// Returns the path to the vscode user settings file
|
||||||
pub fn vscode_settings_file() -> &'static PathBuf {
|
pub fn vscode_settings_file() -> &'static PathBuf {
|
||||||
static LOGS_DIR: OnceLock<PathBuf> = OnceLock::new();
|
static VSCODE_SETTINGS_PATH: OnceLock<PathBuf> = OnceLock::new();
|
||||||
let rel_path = "Code/User/settings.json";
|
VSCODE_SETTINGS_PATH.get_or_init(|| vscode_data_dir().join("User/Settings.json"))
|
||||||
LOGS_DIR.get_or_init(|| {
|
}
|
||||||
|
|
||||||
|
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") {
|
if cfg!(target_os = "macos") {
|
||||||
home_dir()
|
home_dir().join("Library/Application Support/Code")
|
||||||
.join("Library/Application Support")
|
|
||||||
.join(rel_path)
|
|
||||||
} else {
|
} 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()
|
.flex_1()
|
||||||
.p_3()
|
.p_3()
|
||||||
.font_ui(cx)
|
.font_ui(cx)
|
||||||
.text_size(TextSize::Default.rems(cx))
|
.text_size(TextSize::Default.rems())
|
||||||
.children(parsed.children.iter().map(|child| {
|
.children(parsed.children.iter().map(|child| {
|
||||||
div().relative().child(div().relative().child(
|
div().relative().child(div().relative().child(
|
||||||
render_markdown_block(child, &mut markdown_render_context),
|
render_markdown_block(child, &mut markdown_render_context),
|
||||||
@@ -733,7 +733,7 @@ impl Render for RawCell {
|
|||||||
.flex_1()
|
.flex_1()
|
||||||
.p_3()
|
.p_3()
|
||||||
.font_ui(cx)
|
.font_ui(cx)
|
||||||
.text_size(TextSize::Default.rems(cx))
|
.text_size(TextSize::Default.rems())
|
||||||
.child(self.source.clone()),
|
.child(self.source.clone()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ impl ProjectIndexDebugView {
|
|||||||
let chunk = &state.chunks[ix];
|
let chunk = &state.chunks[ix];
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.w_full()
|
.w_full()
|
||||||
.font(buffer_font)
|
.font(buffer_font)
|
||||||
.child(
|
.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.
|
/// 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) {
|
pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
|
||||||
if let Some(selection) = self.icon_theme.as_mut() {
|
if let Some(selection) = self.icon_theme.as_mut() {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ impl RenderOnce for Callout {
|
|||||||
.w_full()
|
.w_full()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.child(message)
|
.child(message)
|
||||||
.text_ui_sm(cx)
|
.text_ui_sm()
|
||||||
.text_color(cx.theme().colors().text_muted),
|
.text_color(cx.theme().colors().text_muted),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ impl RenderOnce for Key {
|
|||||||
let single_char = self.key.len() == 1;
|
let single_char = self.key.len() == 1;
|
||||||
let size = self
|
let size = self
|
||||||
.size
|
.size
|
||||||
.unwrap_or_else(|| TextSize::default().rems(cx).into());
|
.unwrap_or_else(|| TextSize::default().rems().into());
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.py_0()
|
.py_0()
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ impl RenderOnce for KeybindingHint {
|
|||||||
|
|
||||||
let size = self
|
let size = self
|
||||||
.size
|
.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 kb_size = size - px(2.0);
|
||||||
|
|
||||||
let mut base = h_flex();
|
let mut base = h_flex();
|
||||||
|
|||||||
@@ -213,10 +213,10 @@ impl RenderOnce for LabelLike {
|
|||||||
|
|
||||||
self.base
|
self.base
|
||||||
.map(|this| match self.size {
|
.map(|this| match self.size {
|
||||||
LabelSize::Large => this.text_ui_lg(cx),
|
LabelSize::Large => this.text_ui_lg(),
|
||||||
LabelSize::Default => this.text_ui(cx),
|
LabelSize::Default => this.text_ui(),
|
||||||
LabelSize::Small => this.text_ui_sm(cx),
|
LabelSize::Small => this.text_ui_sm(),
|
||||||
LabelSize::XSmall => this.text_ui_xs(cx),
|
LabelSize::XSmall => this.text_ui_xs(),
|
||||||
})
|
})
|
||||||
.when(self.line_height_style == LineHeightStyle::UiLabel, |this| {
|
.when(self.line_height_style == LineHeightStyle::UiLabel, |this| {
|
||||||
this.line_height(relative(1.))
|
this.line_height(relative(1.))
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ impl RenderOnce for AlertModal {
|
|||||||
.p_5()
|
.p_5()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.text_color(Color::Muted.color(cx))
|
.text_color(Color::Muted.color(cx))
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(Headline::new(self.title).size(HeadlineSize::Small))
|
.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 crate::{IntoElement, prelude::*, px, relative};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Along, App, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Corners, Edges, Element,
|
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,
|
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 {
|
pub struct Scrollbar {
|
||||||
thumb: Range<f32>,
|
thumb: Range<f32>,
|
||||||
state: ScrollbarState,
|
state: ScrollbarState,
|
||||||
|
|||||||
@@ -49,12 +49,12 @@ impl Table {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_cell_style(cx: &mut App) -> Div {
|
fn base_cell_style() -> Div {
|
||||||
div()
|
div()
|
||||||
.px_1p5()
|
.px_1p5()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.justify_start()
|
.justify_start()
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.whitespace_nowrap()
|
.whitespace_nowrap()
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
@@ -85,7 +85,7 @@ impl RenderOnce for Table {
|
|||||||
.border_b_1()
|
.border_b_1()
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.children(self.column_headers.into_iter().map(|h| {
|
.children(self.column_headers.into_iter().map(|h| {
|
||||||
Self::base_cell_style(cx)
|
Self::base_cell_style()
|
||||||
.font_weight(FontWeight::SEMIBOLD)
|
.font_weight(FontWeight::SEMIBOLD)
|
||||||
.child(h)
|
.child(h)
|
||||||
}));
|
}));
|
||||||
@@ -111,8 +111,8 @@ impl RenderOnce for Table {
|
|||||||
row.border_b_1().border_color(cx.theme().colors().border)
|
row.border_b_1().border_color(cx.theme().colors().border)
|
||||||
})
|
})
|
||||||
.children(row.into_iter().map(|cell| match cell {
|
.children(row.into_iter().map(|cell| match cell {
|
||||||
TableCell::String(s) => Self::base_cell_style(cx).child(s),
|
TableCell::String(s) => Self::base_cell_style().child(s),
|
||||||
TableCell::Element(e) => Self::base_cell_style(cx).child(e),
|
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()
|
v_flex()
|
||||||
.elevation_2(cx)
|
.elevation_2(cx)
|
||||||
.font(ui_font)
|
.font(ui_font)
|
||||||
.text_ui(cx)
|
.text_ui()
|
||||||
.text_color(cx.theme().colors().text)
|
.text_color(cx.theme().colors().text)
|
||||||
.py_1()
|
.py_1()
|
||||||
.px_2()
|
.px_2()
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ pub trait StyledTypography: Styled + Sized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the text size using a [`TextSize`].
|
/// Sets the text size using a [`TextSize`].
|
||||||
fn text_ui_size(self, size: TextSize, cx: &App) -> Self {
|
fn text_ui_size(self, size: TextSize) -> Self {
|
||||||
self.text_size(size.rems(cx))
|
self.text_size(size.rems())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The large size for UI text.
|
/// 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.
|
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||||
///
|
///
|
||||||
/// Use `text_ui` for regular-sized text.
|
/// Use `text_ui` for regular-sized text.
|
||||||
fn text_ui_lg(self, cx: &App) -> Self {
|
fn text_ui_lg(self) -> Self {
|
||||||
self.text_size(TextSize::Large.rems(cx))
|
self.text_size(TextSize::Large.rems())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default size for UI text.
|
/// 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.
|
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||||
///
|
///
|
||||||
/// Use `text_ui_sm` for smaller text.
|
/// Use `text_ui_sm` for smaller text.
|
||||||
fn text_ui(self, cx: &App) -> Self {
|
fn text_ui(self) -> Self {
|
||||||
self.text_size(TextSize::default().rems(cx))
|
self.text_size(TextSize::default().rems())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The small size for UI text.
|
/// 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.
|
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||||
///
|
///
|
||||||
/// Use `text_ui` for regular-sized text.
|
/// Use `text_ui` for regular-sized text.
|
||||||
fn text_ui_sm(self, cx: &App) -> Self {
|
fn text_ui_sm(self) -> Self {
|
||||||
self.text_size(TextSize::Small.rems(cx))
|
self.text_size(TextSize::Small.rems())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The extra small size for UI text.
|
/// 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.
|
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||||
///
|
///
|
||||||
/// Use `text_ui` for regular-sized text.
|
/// Use `text_ui` for regular-sized text.
|
||||||
fn text_ui_xs(self, cx: &App) -> Self {
|
fn text_ui_xs(self) -> Self {
|
||||||
self.text_size(TextSize::XSmall.rems(cx))
|
self.text_size(TextSize::XSmall.rems())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The font size for buffer text.
|
/// 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.
|
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||||
XSmall,
|
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 {
|
impl TextSize {
|
||||||
/// Returns the text size in rems.
|
/// Returns the text size in rems.
|
||||||
pub fn rems(self, cx: &App) -> Rems {
|
pub fn rems(self) -> Rems {
|
||||||
let theme_settings = ThemeSettings::get_global(cx);
|
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Large => rems_from_px(16.),
|
Self::Large => rems_from_px(16.),
|
||||||
Self::Default => rems_from_px(14.),
|
Self::Default => rems_from_px(14.),
|
||||||
Self::Small => rems_from_px(12.),
|
Self::Small => rems_from_px(12.),
|
||||||
Self::XSmall => rems_from_px(10.),
|
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
|
component.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
documented.workspace = true
|
documented.workspace = true
|
||||||
|
fs.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
install_cli.workspace = true
|
install_cli.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
|
language_model.workspace = true
|
||||||
linkme.workspace = true
|
linkme.workspace = true
|
||||||
|
paths.workspace = true
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
|
regex.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
smol.workspace = true
|
||||||
telemetry.workspace = true
|
telemetry.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
|
time.workspace = true
|
||||||
|
time_format.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
vim_mode_setting.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_picker;
|
||||||
mod base_keymap_setting;
|
mod base_keymap_setting;
|
||||||
mod multibuffer_hint;
|
mod multibuffer_hint;
|
||||||
|
mod recent_projects;
|
||||||
|
mod walkthrough;
|
||||||
mod welcome_ui;
|
mod welcome_ui;
|
||||||
|
|
||||||
actions!(welcome, [ResetHints]);
|
actions!(welcome, [ResetHints]);
|
||||||
@@ -44,6 +46,7 @@ pub fn init(cx: &mut App) {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
walkthrough::init(cx);
|
||||||
base_keymap_picker::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 gpui::{Hsla, Length};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::{Theme, ThemeRegistry};
|
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.selected = selected;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -173,6 +172,7 @@ impl RenderOnce for ThemePreviewTile {
|
|||||||
div()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
|
.rounded(root_radius)
|
||||||
.bg(color.editor_background)
|
.bg(color.editor_background)
|
||||||
.p_2()
|
.p_2()
|
||||||
.child(pseudo_code_skeleton(self.theme.clone(), self.seed)),
|
.child(pseudo_code_skeleton(self.theme.clone(), self.seed)),
|
||||||
@@ -261,7 +261,7 @@ impl Component for ThemePreviewTile {
|
|||||||
themes_to_preview
|
themes_to_preview
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, theme)| {
|
.map(|(_i, theme)| {
|
||||||
div().w(px(200.)).h(px(140.)).child(ThemePreviewTile::new(
|
div().w(px(200.)).h(px(140.)).child(ThemePreviewTile::new(
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ actions!(
|
|||||||
ToggleZoom,
|
ToggleZoom,
|
||||||
Unfollow,
|
Unfollow,
|
||||||
Welcome,
|
Welcome,
|
||||||
|
Walkthrough,
|
||||||
RestoreBanner,
|
RestoreBanner,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ impl Render for ZedPredictModal {
|
|||||||
.border_color(border_color)
|
.border_color(border_color)
|
||||||
.rounded_sm()
|
.rounded_sm()
|
||||||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||||
.text_size(TextSize::XSmall.rems(cx))
|
.text_size(TextSize::XSmall.rems())
|
||||||
.text_color(text_color)
|
.text_color(text_color)
|
||||||
.child("tab")
|
.child("tab")
|
||||||
.with_animation(
|
.with_animation(
|
||||||
|
|||||||
Reference in New Issue
Block a user