Merge remote-tracking branch 'origin/main' into save-conversations

This commit is contained in:
Antonio Scandurra
2023-06-23 09:09:42 +02:00
112 changed files with 4161 additions and 1653 deletions
@@ -326,7 +326,7 @@ impl View for ActivityIndicator {
let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
let theme = &theme::current(cx).workspace.status_bar.lsp_status;
let style = if state.hovered() && on_click.is_some() {
theme.hover.as_ref().unwrap_or(&theme.default)
theme.hovered.as_ref().unwrap_or(&theme.default)
} else {
&theme.default
};
+5 -5
View File
@@ -309,7 +309,7 @@ impl AssistantPanel {
Some(
MouseEventHandler::<Model, _>::new(0, cx, |state, _| {
let style = style.model.style_for(state, false);
let style = style.model.style_for(state);
Label::new(model, style.text.clone())
.contained()
.with_style(style.container)
@@ -414,7 +414,7 @@ impl AssistantPanel {
.with_style(style.title.container),
)
.contained()
.with_style(*style.container.style_for(state, false))
.with_style(*style.container.style_for(state))
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, this, cx| {
@@ -1668,19 +1668,19 @@ impl ConversationEditor {
cx,
|state, _| match message.role {
Role::User => {
let style = style.user_sender.style_for(state, false);
let style = style.user_sender.style_for(state);
Label::new("You", style.text.clone())
.contained()
.with_style(style.container)
}
Role::Assistant => {
let style = style.assistant_sender.style_for(state, false);
let style = style.assistant_sender.style_for(state);
Label::new("Assistant", style.text.clone())
.contained()
.with_style(style.container)
}
Role::System => {
let style = style.system_sender.style_for(state, false);
let style = style.system_sender.style_for(state);
Label::new("System", style.text.clone())
.contained()
.with_style(style.container)
@@ -49,7 +49,7 @@ impl View for UpdateNotification {
)
.with_child(
MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false);
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
.constrained()
@@ -74,7 +74,7 @@ impl View for UpdateNotification {
),
)
.with_child({
let style = theme.action_message.style_for(state, false);
let style = theme.action_message.style_for(state);
Text::new("View the release notes", style.text.clone())
.contained()
.with_style(style.container)
+1 -1
View File
@@ -83,7 +83,7 @@ impl View for Breadcrumbs {
}
MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
let style = style.style_for(state, false);
let style = style.style_for(state);
crumbs.with_style(style.container)
})
.on_click(MouseButton::Left, |_, this, cx| {
+26 -8
View File
@@ -299,7 +299,12 @@ impl CollabTitlebarItem {
pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
let theme = theme::current(cx).clone();
let avatar_style = theme.workspace.titlebar.leader_avatar.clone();
let item_style = theme.context_menu.item.disabled_style().clone();
let item_style = theme
.context_menu
.item
.inactive_state()
.disabled_style()
.clone();
self.user_menu.update(cx, |user_menu, cx| {
let items = if let Some(user) = self.user_store.read(cx).current_user() {
vec![
@@ -361,8 +366,20 @@ impl CollabTitlebarItem {
.contained()
.with_style(titlebar.toggle_contacts_badge)
.contained()
.with_margin_left(titlebar.toggle_contacts_button.default.icon_width)
.with_margin_top(titlebar.toggle_contacts_button.default.icon_width)
.with_margin_left(
titlebar
.toggle_contacts_button
.inactive_state()
.default
.icon_width,
)
.with_margin_top(
titlebar
.toggle_contacts_button
.inactive_state()
.default
.icon_width,
)
.aligned(),
)
};
@@ -372,7 +389,8 @@ impl CollabTitlebarItem {
MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
let style = titlebar
.toggle_contacts_button
.style_for(state, self.contacts_popover.is_some());
.in_state(self.contacts_popover.is_some())
.style_for(state);
Svg::new("icons/user_plus_16.svg")
.with_color(style.color)
.constrained()
@@ -419,7 +437,7 @@ impl CollabTitlebarItem {
let titlebar = &theme.workspace.titlebar;
MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
let style = titlebar.call_control.style_for(state, false);
let style = titlebar.call_control.style_for(state);
Svg::new(icon)
.with_color(style.color)
.constrained()
@@ -473,7 +491,7 @@ impl CollabTitlebarItem {
.with_child(
MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
//TODO: Ensure this button has consistent width for both text variations
let style = titlebar.share_button.style_for(state, false);
let style = titlebar.share_button.inactive_state().style_for(state);
Label::new(label, style.text.clone())
.contained()
.with_style(style.container)
@@ -511,7 +529,7 @@ impl CollabTitlebarItem {
Stack::new()
.with_child(
MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
let style = titlebar.call_control.style_for(state, false);
let style = titlebar.call_control.style_for(state);
Svg::new("icons/ellipsis_14.svg")
.with_color(style.color)
.constrained()
@@ -549,7 +567,7 @@ impl CollabTitlebarItem {
fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let titlebar = &theme.workspace.titlebar;
MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
let style = titlebar.sign_in_prompt.style_for(state, false);
let style = titlebar.sign_in_prompt.inactive_state().style_for(state);
Label::new("Sign In", style.text.clone())
.contained()
.with_style(style.container)
+2 -1
View File
@@ -117,7 +117,8 @@ impl PickerDelegate for ContactFinderDelegate {
.contact_finder
.picker
.item
.style_for(mouse_state, selected);
.in_state(selected)
.style_for(mouse_state);
Flex::row()
.with_children(user.avatar.clone().map(|avatar| {
Image::from_data(avatar)
+25 -16
View File
@@ -774,7 +774,8 @@ impl ContactList {
.with_style(
*theme
.contact_row
.style_for(&mut Default::default(), is_selected),
.in_state(is_selected)
.style_for(&mut Default::default()),
)
.into_any()
}
@@ -797,7 +798,7 @@ impl ContactList {
.width
.or(theme.contact_avatar.height)
.unwrap_or(0.);
let row = &theme.project_row.default;
let row = &theme.project_row.inactive_state().default;
let tree_branch = theme.tree_branch;
let line_height = row.name.text.line_height(font_cache);
let cap_height = row.name.text.cap_height(font_cache);
@@ -810,8 +811,11 @@ impl ContactList {
};
MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
let row = theme.project_row.style_for(mouse_state, is_selected);
let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
let row = theme
.project_row
.in_state(is_selected)
.style_for(mouse_state);
Flex::row()
.with_child(
@@ -893,7 +897,7 @@ impl ContactList {
.width
.or(theme.contact_avatar.height)
.unwrap_or(0.);
let row = &theme.project_row.default;
let row = &theme.project_row.inactive_state().default;
let tree_branch = theme.tree_branch;
let line_height = row.name.text.line_height(font_cache);
let cap_height = row.name.text.cap_height(font_cache);
@@ -904,8 +908,11 @@ impl ContactList {
peer_id.as_u64() as usize,
cx,
|mouse_state, _| {
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
let row = theme.project_row.style_for(mouse_state, is_selected);
let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
let row = theme
.project_row
.in_state(is_selected)
.style_for(mouse_state);
Flex::row()
.with_child(
@@ -989,7 +996,8 @@ impl ContactList {
let header_style = theme
.header_row
.style_for(&mut Default::default(), is_selected);
.in_state(is_selected)
.style_for(&mut Default::default());
let text = match section {
Section::ActiveCall => "Collaborators",
Section::Requests => "Contact Requests",
@@ -999,7 +1007,7 @@ impl ContactList {
let leave_call = if section == Section::ActiveCall {
Some(
MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
let style = theme.leave_call.style_for(state, false);
let style = theme.leave_call.style_for(state);
Label::new("Leave Call", style.text.clone())
.contained()
.with_style(style.container)
@@ -1110,8 +1118,7 @@ impl ContactList {
contact.user.id as usize,
cx,
|mouse_state, _| {
let button_style =
theme.contact_button.style_for(mouse_state, false);
let button_style = theme.contact_button.style_for(mouse_state);
render_icon_button(button_style, "icons/x_mark_8.svg")
.aligned()
.flex_float()
@@ -1146,7 +1153,8 @@ impl ContactList {
.with_style(
*theme
.contact_row
.style_for(&mut Default::default(), is_selected),
.in_state(is_selected)
.style_for(&mut Default::default()),
)
})
.on_click(MouseButton::Left, move |_, this, cx| {
@@ -1204,7 +1212,7 @@ impl ContactList {
let button_style = if is_contact_request_pending {
&theme.disabled_button
} else {
theme.contact_button.style_for(mouse_state, false)
theme.contact_button.style_for(mouse_state)
};
render_icon_button(button_style, "icons/x_mark_8.svg").aligned()
})
@@ -1227,7 +1235,7 @@ impl ContactList {
let button_style = if is_contact_request_pending {
&theme.disabled_button
} else {
theme.contact_button.style_for(mouse_state, false)
theme.contact_button.style_for(mouse_state)
};
render_icon_button(button_style, "icons/check_8.svg")
.aligned()
@@ -1250,7 +1258,7 @@ impl ContactList {
let button_style = if is_contact_request_pending {
&theme.disabled_button
} else {
theme.contact_button.style_for(mouse_state, false)
theme.contact_button.style_for(mouse_state)
};
render_icon_button(button_style, "icons/x_mark_8.svg")
.aligned()
@@ -1277,7 +1285,8 @@ impl ContactList {
.with_style(
*theme
.contact_row
.style_for(&mut Default::default(), is_selected),
.in_state(is_selected)
.style_for(&mut Default::default()),
)
.into_any()
}
+2 -2
View File
@@ -53,7 +53,7 @@ where
)
.with_child(
MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false);
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
.constrained()
@@ -93,7 +93,7 @@ where
.with_children(buttons.into_iter().enumerate().map(
|(ix, (message, handler))| {
MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
let button = theme.button.style_for(state, false);
let button = theme.button.style_for(state);
Label::new(message, button.text.clone())
.contained()
.with_style(button.container)
@@ -185,8 +185,8 @@ impl PickerDelegate for CommandPaletteDelegate {
let mat = &self.matches[ix];
let command = &self.actions[mat.candidate_id];
let theme = theme::current(cx);
let style = theme.picker.item.style_for(mouse_state, selected);
let key_style = &theme.command_palette.key.style_for(mouse_state, selected);
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
let key_style = &theme.command_palette.key.in_state(selected);
let keystroke_spacing = theme.command_palette.keystroke_spacing;
Flex::row()
+6 -10
View File
@@ -328,10 +328,8 @@ impl ContextMenu {
Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item {
ContextMenuItem::Item { label, .. } => {
let style = style.item.style_for(
&mut Default::default(),
Some(ix) == self.selected_index,
);
let style = style.item.in_state(self.selected_index == Some(ix));
let style = style.style_for(&mut Default::default());
match label {
ContextMenuItemLabel::String(label) => {
@@ -363,10 +361,8 @@ impl ContextMenu {
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item {
ContextMenuItem::Item { action, .. } => {
let style = style.item.style_for(
&mut Default::default(),
Some(ix) == self.selected_index,
);
let style = style.item.in_state(self.selected_index == Some(ix));
let style = style.style_for(&mut Default::default());
match action {
ContextMenuItemAction::Action(action) => KeystrokeLabel::new(
@@ -412,8 +408,8 @@ impl ContextMenu {
let action = action.clone();
let view_id = self.parent_view_id;
MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
let style =
style.item.style_for(state, Some(ix) == self.selected_index);
let style = style.item.in_state(self.selected_index == Some(ix));
let style = style.style_for(state);
let keystroke = match &action {
ContextMenuItemAction::Action(action) => Some(
KeystrokeLabel::new(
+3 -3
View File
@@ -127,16 +127,16 @@ impl CopilotCodeVerification {
.with_child(
Label::new(
if copied { "Copied!" } else { "Copy" },
device_code_style.cta.style_for(state, false).text.clone(),
device_code_style.cta.style_for(state).text.clone(),
)
.aligned()
.contained()
.with_style(*device_code_style.right_container.style_for(state, false))
.with_style(*device_code_style.right_container.style_for(state))
.constrained()
.with_width(device_code_style.right),
)
.contained()
.with_style(device_code_style.cta.style_for(state, false).container)
.with_style(device_code_style.cta.style_for(state).container)
})
.on_click(gpui::platform::MouseButton::Left, {
let user_code = data.user_code.clone();
+3 -2
View File
@@ -71,7 +71,8 @@ impl View for CopilotButton {
.status_bar
.panel_buttons
.button
.style_for(state, active);
.in_state(active)
.style_for(state);
Flex::row()
.with_child(
@@ -255,7 +256,7 @@ impl CopilotButton {
move |state: &mut MouseState, style: &theme::ContextMenuItem| {
Flex::row()
.with_child(Label::new("Copilot Settings", style.label.clone()))
.with_child(theme::ui::icon(icon_style.style_for(state, false)))
.with_child(theme::ui::icon(icon_style.style_for(state)))
.align_children_center()
.into_any()
},
+3 -1
View File
@@ -1509,7 +1509,8 @@ mod tests {
let snapshot = editor.snapshot(cx);
snapshot
.blocks_in_range(0..snapshot.max_point().row())
.filter_map(|(row, block)| {
.enumerate()
.filter_map(|(ix, (row, block))| {
let name = match block {
TransformBlock::Custom(block) => block
.render(&mut BlockContext {
@@ -1520,6 +1521,7 @@ mod tests {
gutter_width: 0.,
line_height: 0.,
em_width: 0.,
block_id: ix,
})
.name()?
.to_string(),
+2 -2
View File
@@ -100,7 +100,7 @@ impl View for DiagnosticIndicator {
.workspace
.status_bar
.diagnostic_summary
.style_for(state, false);
.style_for(state);
let mut summary_row = Flex::row();
if self.summary.error_count > 0 {
@@ -198,7 +198,7 @@ impl View for DiagnosticIndicator {
MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
Label::new(
diagnostic.message.split('\n').next().unwrap().to_string(),
message_style.style_for(state, false).text.clone(),
message_style.style_for(state).text.clone(),
)
.aligned()
.contained()
@@ -88,6 +88,7 @@ pub struct BlockContext<'a, 'b, 'c> {
pub gutter_padding: f32,
pub em_width: f32,
pub line_height: f32,
pub block_id: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+101 -21
View File
@@ -206,6 +206,7 @@ actions!(
DuplicateLine,
MoveLineUp,
MoveLineDown,
JoinLines,
Transpose,
Cut,
Copy,
@@ -321,6 +322,7 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::indent);
cx.add_action(Editor::outdent);
cx.add_action(Editor::delete_line);
cx.add_action(Editor::join_lines);
cx.add_action(Editor::delete_to_previous_word_start);
cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end);
@@ -3320,15 +3322,21 @@ impl Editor {
pub fn render_code_actions_indicator(
&self,
style: &EditorStyle,
active: bool,
is_active: bool,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement<Self>> {
if self.available_code_actions.is_some() {
enum CodeActions {}
Some(
MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
Svg::new("icons/bolt_8.svg")
.with_color(style.code_actions.indicator.style_for(state, active).color)
Svg::new("icons/bolt_8.svg").with_color(
style
.code_actions
.indicator
.in_state(is_active)
.style_for(state)
.color,
)
})
.with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(3.))
@@ -3378,10 +3386,8 @@ impl Editor {
.with_color(
style
.indicator
.style_for(
mouse_state,
fold_status == FoldStatus::Folded,
)
.in_state(fold_status == FoldStatus::Folded)
.style_for(mouse_state)
.color,
)
.constrained()
@@ -3952,6 +3958,60 @@ impl Editor {
});
}
pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
let mut row_ranges = Vec::<Range<u32>>::new();
for selection in self.selections.all::<Point>(cx) {
let start = selection.start.row;
let end = if selection.start.row == selection.end.row {
selection.start.row + 1
} else {
selection.end.row
};
if let Some(last_row_range) = row_ranges.last_mut() {
if start <= last_row_range.end {
last_row_range.end = end;
continue;
}
}
row_ranges.push(start..end);
}
let snapshot = self.buffer.read(cx).snapshot(cx);
let mut cursor_positions = Vec::new();
for row_range in &row_ranges {
let anchor = snapshot.anchor_before(Point::new(
row_range.end - 1,
snapshot.line_len(row_range.end - 1),
));
cursor_positions.push(anchor.clone()..anchor);
}
self.transact(cx, |this, cx| {
for row_range in row_ranges.into_iter().rev() {
for row in row_range.rev() {
let end_of_line = Point::new(row, snapshot.line_len(row));
let indent = snapshot.indent_size_for_line(row + 1);
let start_of_next_line = Point::new(row + 1, indent.len);
let replace = if snapshot.line_len(row + 1) > indent.len {
" "
} else {
""
};
this.buffer.update(cx, |buffer, cx| {
buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
});
}
}
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_anchor_ranges(cursor_positions)
});
});
}
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
@@ -7949,6 +8009,7 @@ impl Deref for EditorStyle {
pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> RenderBlock {
let mut highlighted_lines = Vec::new();
for (index, line) in diagnostic.message.lines().enumerate() {
let line = match &diagnostic.source {
Some(source) if index == 0 => {
@@ -7960,25 +8021,44 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
};
highlighted_lines.push(line);
}
let message = diagnostic.message;
Arc::new(move |cx: &mut BlockContext| {
let message = message.clone();
let settings = settings::get::<ThemeSettings>(cx);
let tooltip_style = settings.theme.tooltip.clone();
let theme = &settings.theme.editor;
let style = diagnostic_style(diagnostic.severity, is_valid, theme);
let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
Flex::column()
.with_children(highlighted_lines.iter().map(|(line, highlights)| {
Label::new(
line.clone(),
style.message.clone().with_font_size(font_size),
)
.with_highlights(highlights.clone())
.contained()
.with_margin_left(cx.anchor_x)
}))
.aligned()
.left()
.into_any()
let anchor_x = cx.anchor_x;
enum BlockContextToolip {}
MouseEventHandler::<BlockContext, _>::new(cx.block_id, cx, |_, _| {
Flex::column()
.with_children(highlighted_lines.iter().map(|(line, highlights)| {
Label::new(
line.clone(),
style.message.clone().with_font_size(font_size),
)
.with_highlights(highlights.clone())
.contained()
.with_margin_left(anchor_x)
}))
.aligned()
.left()
.into_any()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, _, cx| {
cx.write_to_clipboard(ClipboardItem::new(message.clone()));
})
// We really need to rethink this ID system...
.with_tooltip::<BlockContextToolip>(
cx.block_id,
"Copy diagnostic message".to_string(),
None,
tooltip_style,
cx,
)
.into_any()
})
}
+137 -3
View File
@@ -1,7 +1,10 @@
use super::*;
use crate::test::{
assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
editor_test_context::EditorTestContext, select_ranges,
use crate::{
test::{
assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
editor_test_context::EditorTestContext, select_ranges,
},
JoinLines,
};
use drag_and_drop::DragAndDrop;
use futures::StreamExt;
@@ -2325,6 +2328,137 @@ fn test_delete_line(cx: &mut TestAppContext) {
});
}
#[gpui::test]
fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
init_test(cx, |_| {});
cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
let mut editor = build_editor(buffer.clone(), cx);
let buffer = buffer.read(cx).as_singleton().unwrap();
assert_eq!(
editor.selections.ranges::<Point>(cx),
&[Point::new(0, 0)..Point::new(0, 0)]
);
// When on single line, replace newline at end by space
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
assert_eq!(
editor.selections.ranges::<Point>(cx),
&[Point::new(0, 3)..Point::new(0, 3)]
);
// When multiple lines are selected, remove newlines that are spanned by the selection
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
});
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
assert_eq!(
editor.selections.ranges::<Point>(cx),
&[Point::new(0, 11)..Point::new(0, 11)]
);
// Undo should be transactional
editor.undo(&Undo, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
assert_eq!(
editor.selections.ranges::<Point>(cx),
&[Point::new(0, 5)..Point::new(2, 2)]
);
// When joining an empty line don't insert a space
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
});
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
assert_eq!(
editor.selections.ranges::<Point>(cx),
[Point::new(2, 3)..Point::new(2, 3)]
);
// We can remove trailing newlines
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
assert_eq!(
editor.selections.ranges::<Point>(cx),
[Point::new(2, 3)..Point::new(2, 3)]
);
// We don't blow up on the last line
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
assert_eq!(
editor.selections.ranges::<Point>(cx),
[Point::new(2, 3)..Point::new(2, 3)]
);
// reset to test indentation
editor.buffer.update(cx, |buffer, cx| {
buffer.edit(
[
(Point::new(1, 0)..Point::new(1, 2), " "),
(Point::new(2, 0)..Point::new(2, 3), " \n\td"),
],
None,
cx,
)
});
// We remove any leading spaces
assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
});
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
// We don't insert a space for a line containing only spaces
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
// We ignore any leading tabs
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
editor
});
}
#[gpui::test]
fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
init_test(cx, |_| {});
cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
let mut editor = build_editor(buffer.clone(), cx);
let buffer = buffer.read(cx).as_singleton().unwrap();
editor.change_selections(None, cx, |s| {
s.select_ranges([
Point::new(0, 2)..Point::new(1, 1),
Point::new(1, 2)..Point::new(1, 2),
Point::new(3, 1)..Point::new(3, 2),
])
});
editor.join_lines(&JoinLines, cx);
assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
assert_eq!(
editor.selections.ranges::<Point>(cx),
[
Point::new(0, 7)..Point::new(0, 7),
Point::new(1, 3)..Point::new(1, 3)
]
);
editor
});
}
#[gpui::test]
fn test_duplicate_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
+9 -5
View File
@@ -1467,6 +1467,7 @@ impl EditorElement {
editor: &mut Editor,
cx: &mut LayoutContext<Editor>,
) -> (f32, Vec<BlockLayout>) {
let mut block_id = 0;
let scroll_x = snapshot.scroll_anchor.offset.x();
let (fixed_blocks, non_fixed_blocks) = snapshot
.blocks_in_range(rows.clone())
@@ -1474,7 +1475,7 @@ impl EditorElement {
TransformBlock::ExcerptHeader { .. } => false,
TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
});
let mut render_block = |block: &TransformBlock, width: f32| {
let mut render_block = |block: &TransformBlock, width: f32, block_id: usize| {
let mut element = match block {
TransformBlock::Custom(block) => {
let align_to = block
@@ -1499,6 +1500,7 @@ impl EditorElement {
scroll_x,
gutter_width,
em_width,
block_id,
})
}
TransformBlock::ExcerptHeader {
@@ -1527,7 +1529,7 @@ impl EditorElement {
enum JumpIcon {}
MouseEventHandler::<JumpIcon, _>::new((*id).into(), cx, |state, _| {
let style = style.jump_icon.style_for(state, false);
let style = style.jump_icon.style_for(state);
Svg::new("icons/arrow_up_right_8.svg")
.with_color(style.color)
.constrained()
@@ -1634,7 +1636,8 @@ impl EditorElement {
let mut fixed_block_max_width = 0f32;
let mut blocks = Vec::new();
for (row, block) in fixed_blocks {
let element = render_block(block, f32::INFINITY);
let element = render_block(block, f32::INFINITY, block_id);
block_id += 1;
fixed_block_max_width = fixed_block_max_width.max(element.size().x() + em_width);
blocks.push(BlockLayout {
row,
@@ -1654,7 +1657,8 @@ impl EditorElement {
.max(gutter_width + scroll_width),
BlockStyle::Fixed => unreachable!(),
};
let element = render_block(block, width);
let element = render_block(block, width, block_id);
block_id += 1;
blocks.push(BlockLayout {
row,
element,
@@ -2090,7 +2094,7 @@ impl Element<Editor> for EditorElement {
.folds
.ellipses
.background
.style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize), false)
.style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
.color;
(id, fold, color)
@@ -41,7 +41,8 @@ impl View for DeployFeedbackButton {
.status_bar
.panel_buttons
.button
.style_for(state, active);
.in_state(active)
.style_for(state);
Svg::new("icons/feedback_16.svg")
.with_color(style.icon_color)
@@ -48,7 +48,7 @@ impl View for SubmitFeedbackButton {
let theme = theme::current(cx).clone();
enum SubmitFeedbackButton {}
MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
let style = theme.feedback.submit_button.style_for(state, false);
let style = theme.feedback.submit_button.style_for(state);
Label::new("Submit as Markdown", style.text.clone())
.contained()
.with_style(style.container)
+1 -1
View File
@@ -546,7 +546,7 @@ impl PickerDelegate for FileFinderDelegate {
.get(ix)
.expect("Invalid matches state: no element for index {ix}");
let theme = theme::current(cx);
let style = theme.picker.item.style_for(mouse_state, selected);
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
let (file_name, file_name_positions, full_path, full_path_positions) =
self.labels_for_match(path_match, cx, ix);
Flex::column()
+3 -2
View File
@@ -6,15 +6,16 @@ use std::{
use crate::json::ToJson;
use pathfinder_color::{ColorF, ColorU};
use schemars::JsonSchema;
use serde::{
de::{self, Unexpected},
Deserialize, Deserializer,
};
use serde_json::json;
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)]
#[repr(transparent)]
pub struct Color(ColorU);
pub struct Color(#[schemars(with = "String")] ColorU);
impl Color {
pub fn transparent_black() -> Self {
+6 -4
View File
@@ -12,10 +12,11 @@ use crate::{
scene::{self, Border, CursorRegion, Quad},
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::json;
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
pub struct ContainerStyle {
#[serde(default)]
pub margin: Margin,
@@ -332,7 +333,7 @@ impl ToJson for ContainerStyle {
}
}
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug, Default, JsonSchema)]
pub struct Margin {
pub top: f32,
pub left: f32,
@@ -359,7 +360,7 @@ impl ToJson for Margin {
}
}
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug, Default, JsonSchema)]
pub struct Padding {
pub top: f32,
pub left: f32,
@@ -486,9 +487,10 @@ impl ToJson for Padding {
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
pub struct Shadow {
#[serde(default, deserialize_with = "deserialize_vec2f")]
#[schemars(with = "Vec::<f32>")]
offset: Vector2F,
#[serde(default)]
blur: f32,
+2 -1
View File
@@ -8,6 +8,7 @@ use crate::{
scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View,
ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
use std::{ops::Range, sync::Arc};
@@ -21,7 +22,7 @@ pub struct Image {
style: ImageStyle,
}
#[derive(Copy, Clone, Default, Deserialize)]
#[derive(Copy, Clone, Default, Deserialize, JsonSchema)]
pub struct ImageStyle {
#[serde(default)]
pub border: Border,
+2 -1
View File
@@ -10,6 +10,7 @@ use crate::{
text_layout::{Line, RunStyle},
Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::json;
use smallvec::{smallvec, SmallVec};
@@ -20,7 +21,7 @@ pub struct Label {
highlight_indices: Vec<usize>,
}
#[derive(Clone, Debug, Deserialize, Default)]
#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
pub struct LabelStyle {
pub text: TextStyle,
pub highlight_text: Option<TextStyle>,
+3 -2
View File
@@ -8,6 +8,7 @@ use crate::{
},
scene, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use schemars::JsonSchema;
use serde_derive::Deserialize;
use serde_json::json;
use std::{borrow::Cow, ops::Range};
@@ -115,14 +116,14 @@ impl<V: View> Element<V> for Svg {
}
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct SvgStyle {
pub color: Color,
pub asset: String,
pub dimensions: Dimensions,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Dimensions {
pub width: f32,
pub height: f32,
+3 -2
View File
@@ -9,6 +9,7 @@ use crate::{
Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View,
ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
use std::{
cell::{Cell, RefCell},
@@ -33,7 +34,7 @@ struct TooltipState {
debounce: RefCell<Option<Task<()>>>,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct TooltipStyle {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -42,7 +43,7 @@ pub struct TooltipStyle {
pub max_text_width: Option<f32>,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct KeystrokeStyle {
#[serde(flatten)]
container: ContainerStyle,
+2 -1
View File
@@ -7,13 +7,14 @@ use crate::{
use anyhow::{anyhow, Result};
use ordered_float::OrderedFloat;
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use schemars::JsonSchema;
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::Arc,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
pub struct FamilyId(usize);
struct Family {
+29 -4
View File
@@ -16,7 +16,7 @@ use serde::{de, Deserialize, Serialize};
use serde_json::Value;
use std::{cell::RefCell, sync::Arc};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
pub struct FontId(pub usize);
pub type GlyphId = u32;
@@ -59,20 +59,44 @@ pub struct Features {
pub zero: Option<bool>,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, JsonSchema)]
pub struct TextStyle {
pub color: Color,
pub font_family_name: Arc<str>,
pub font_family_id: FamilyId,
pub font_id: FontId,
pub font_size: f32,
#[schemars(with = "PropertiesDef")]
pub font_properties: Properties,
pub underline: Underline,
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[derive(JsonSchema)]
#[serde(remote = "Properties")]
pub struct PropertiesDef {
/// The font style, as defined in CSS.
pub style: StyleDef,
/// The font weight, as defined in CSS.
pub weight: f32,
/// The font stretchiness, as defined in CSS.
pub stretch: f32,
}
#[derive(JsonSchema)]
#[schemars(remote = "Style")]
pub enum StyleDef {
/// A face that is neither italic not obliqued.
Normal,
/// A form that is generally cursive in nature.
Italic,
/// A typically-sloped version of the regular face.
Oblique,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, JsonSchema)]
pub struct HighlightStyle {
pub color: Option<Color>,
#[schemars(with = "Option::<f32>")]
pub weight: Option<Weight>,
pub italic: Option<bool>,
pub underline: Option<Underline>,
@@ -81,9 +105,10 @@ pub struct HighlightStyle {
impl Eq for HighlightStyle {}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, JsonSchema)]
pub struct Underline {
pub color: Option<Color>,
#[schemars(with = "f32")]
pub thickness: OrderedFloat<f32>,
pub squiggly: bool,
}
+2 -1
View File
@@ -25,6 +25,7 @@ use anyhow::{anyhow, bail, Result};
use async_task::Runnable;
pub use event::*;
use postage::oneshot;
use schemars::JsonSchema;
use serde::Deserialize;
use sqlez::{
bindable::{Bind, Column, StaticColumnCount},
@@ -282,7 +283,7 @@ pub enum PromptLevel {
Critical,
}
#[derive(Copy, Clone, Debug, Deserialize)]
#[derive(Copy, Clone, Debug, Deserialize, JsonSchema)]
pub enum CursorStyle {
Arrow,
ResizeLeftRight,
+2 -1
View File
@@ -3,6 +3,7 @@ mod mouse_region;
#[cfg(debug_assertions)]
use collections::HashSet;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::json;
use std::{borrow::Cow, sync::Arc};
@@ -99,7 +100,7 @@ pub struct Icon {
pub color: Color,
}
#[derive(Clone, Copy, Default, Debug)]
#[derive(Clone, Copy, Default, Debug, JsonSchema)]
pub struct Border {
pub width: f32,
pub color: Color,
@@ -55,7 +55,7 @@ impl View for ActiveBufferLanguage {
MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
let theme = &theme::current(cx).workspace.status_bar;
let style = theme.active_language.style_for(state, false);
let style = theme.active_language.style_for(state);
Label::new(active_language_text, style.text.clone())
.contained()
.with_style(style.container)
@@ -180,7 +180,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
) -> AnyElement<Picker<Self>> {
let theme = theme::current(cx);
let mat = &self.matches[ix];
let style = theme.picker.item.style_for(mouse_state, selected);
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
let mut label = mat.string.clone();
if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
+5 -3
View File
@@ -681,7 +681,7 @@ impl LspLogToolbarItemView {
)
})
.unwrap_or_else(|| "No server selected".into());
let style = theme.toolbar_dropdown_menu.header.style_for(state, false);
let style = theme.toolbar_dropdown_menu.header.style_for(state);
Label::new(label, style.text.clone())
.contained()
.with_style(style.container)
@@ -722,7 +722,8 @@ impl LspLogToolbarItemView {
let style = theme
.toolbar_dropdown_menu
.item
.style_for(state, logs_selected);
.in_state(logs_selected)
.style_for(state);
Label::new(SERVER_LOGS, style.text.clone())
.contained()
.with_style(style.container)
@@ -739,7 +740,8 @@ impl LspLogToolbarItemView {
let style = theme
.toolbar_dropdown_menu
.item
.style_for(state, rpc_trace_selected);
.in_state(rpc_trace_selected)
.style_for(state);
Flex::row()
.with_child(
Label::new(RPC_MESSAGES, style.text.clone())
@@ -565,7 +565,7 @@ impl SyntaxTreeToolbarItemView {
) -> impl Element<Self> {
enum ToggleMenu {}
MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, _| {
let style = theme.toolbar_dropdown_menu.header.style_for(state, false);
let style = theme.toolbar_dropdown_menu.header.style_for(state);
Flex::row()
.with_child(
Label::new(active_layer.language.name().to_string(), style.text.clone())
@@ -601,7 +601,8 @@ impl SyntaxTreeToolbarItemView {
let style = theme
.toolbar_dropdown_menu
.item
.style_for(state, is_selected);
.in_state(is_selected)
.style_for(state);
Flex::row()
.with_child(
Label::new(layer.language.name().to_string(), style.text.clone())
+18 -9
View File
@@ -33,7 +33,7 @@ const JSON_RPC_VERSION: &str = "2.0";
const CONTENT_LEN_HEADER: &str = "Content-Length: ";
type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppContext)>;
type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
type IoHandler = Box<dyn Send + FnMut(bool, &str)>;
pub struct LanguageServer {
@@ -302,9 +302,9 @@ impl LanguageServer {
if let Some(error) = error {
handler(Err(error));
} else if let Some(result) = result {
handler(Ok(result.get()));
handler(Ok(result.get().into()));
} else {
handler(Ok("null"));
handler(Ok("null".into()));
}
}
} else {
@@ -457,11 +457,13 @@ impl LanguageServer {
let response_handlers = self.response_handlers.clone();
let next_id = AtomicUsize::new(self.next_id.load(SeqCst));
let outbound_tx = self.outbound_tx.clone();
let executor = self.executor.clone();
let mut output_done = self.output_done_rx.lock().take().unwrap();
let shutdown_request = Self::request_internal::<request::Shutdown>(
&next_id,
&response_handlers,
&outbound_tx,
&executor,
(),
);
let exit = Self::notify_internal::<notification::Exit>(&outbound_tx, ());
@@ -658,6 +660,7 @@ impl LanguageServer {
&self.next_id,
&self.response_handlers,
&self.outbound_tx,
&self.executor,
params,
)
}
@@ -666,6 +669,7 @@ impl LanguageServer {
next_id: &AtomicUsize,
response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
outbound_tx: &channel::Sender<String>,
executor: &Arc<executor::Background>,
params: T::Params,
) -> impl 'static + Future<Output = Result<T::Result>>
where
@@ -686,15 +690,20 @@ impl LanguageServer {
.as_mut()
.ok_or_else(|| anyhow!("server shut down"))
.map(|handlers| {
let executor = executor.clone();
handlers.insert(
id,
Box::new(move |result| {
let response = match result {
Ok(response) => serde_json::from_str(response)
.context("failed to deserialize response"),
Err(error) => Err(anyhow!("{}", error.message)),
};
let _ = tx.send(response);
executor
.spawn(async move {
let response = match result {
Ok(response) => serde_json::from_str(&response)
.context("failed to deserialize response"),
Err(error) => Err(anyhow!("{}", error.message)),
};
let _ = tx.send(response);
})
.detach();
}),
);
});
+1 -1
View File
@@ -204,7 +204,7 @@ impl PickerDelegate for OutlineViewDelegate {
cx: &AppContext,
) -> AnyElement<Picker<Self>> {
let theme = theme::current(cx);
let style = theme.picker.item.style_for(mouse_state, selected);
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
let string_match = &self.matches[ix];
let outline_item = &self.outline.items[string_match.candidate_id];
+10 -5
View File
@@ -1254,7 +1254,10 @@ impl ProjectPanel {
let show_editor = details.is_editing && !details.is_processing;
MouseEventHandler::<Self, _>::new(entry_id.to_usize(), cx, |state, cx| {
let mut style = entry_style.style_for(state, details.is_selected).clone();
let mut style = entry_style
.in_state(details.is_selected)
.style_for(state)
.clone();
if cx
.global::<DragAndDrop<Workspace>>()
@@ -1265,7 +1268,7 @@ impl ProjectPanel {
.filter(|destination| details.path.starts_with(destination))
.is_some()
{
style = entry_style.active.clone().unwrap();
style = entry_style.active_state().default.clone();
}
let row_container_style = if show_editor {
@@ -1406,9 +1409,11 @@ impl View for ProjectPanel {
let button_style = theme.open_project_button.clone();
let context_menu_item_style = theme::current(cx).context_menu.item.clone();
move |state, cx| {
let button_style = button_style.style_for(state, false).clone();
let context_menu_item =
context_menu_item_style.style_for(state, true).clone();
let button_style = button_style.style_for(state).clone();
let context_menu_item = context_menu_item_style
.active_state()
.style_for(state)
.clone();
theme::ui::keystroke_label(
"Open a project",
@@ -196,7 +196,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
) -> AnyElement<Picker<Self>> {
let theme = theme::current(cx);
let style = &theme.picker.item;
let current_style = style.style_for(mouse_state, selected);
let current_style = style.in_state(selected).style_for(mouse_state);
let string_match = &self.matches[ix];
let symbol = &self.symbols[string_match.candidate_id];
@@ -229,7 +229,10 @@ impl PickerDelegate for ProjectSymbolsDelegate {
.with_child(
// Avoid styling the path differently when it is selected, since
// the symbol's syntax highlighting doesn't change when selected.
Label::new(path.to_string(), style.default.label.clone()),
Label::new(
path.to_string(),
style.inactive_state().default.label.clone(),
),
)
.contained()
.with_style(current_style.container)
@@ -173,7 +173,7 @@ impl PickerDelegate for RecentProjectsDelegate {
cx: &gpui::AppContext,
) -> AnyElement<Picker<Self>> {
let theme = theme::current(cx);
let style = theme.picker.item.style_for(mouse_state, selected);
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
let string_match = &self.matches[ix];
+7 -3
View File
@@ -328,7 +328,11 @@ impl BufferSearchBar {
Some(
MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme.search.option_button.style_for(state, is_active);
let style = theme
.search
.option_button
.in_state(is_active)
.style_for(state);
Label::new(icon, style.text.clone())
.contained()
.with_style(style.container)
@@ -371,7 +375,7 @@ impl BufferSearchBar {
enum NavButton {}
MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme.search.option_button.style_for(state, false);
let style = theme.search.option_button.inactive_state().style_for(state);
Label::new(icon, style.text.clone())
.contained()
.with_style(style.container)
@@ -403,7 +407,7 @@ impl BufferSearchBar {
enum CloseButton {}
MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false);
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
.constrained()
+6 -2
View File
@@ -896,7 +896,7 @@ impl ProjectSearchBar {
enum NavButton {}
MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme.search.option_button.style_for(state, false);
let style = theme.search.option_button.inactive_state().style_for(state);
Label::new(icon, style.text.clone())
.contained()
.with_style(style.container)
@@ -927,7 +927,11 @@ impl ProjectSearchBar {
let is_active = self.is_option_enabled(option, cx);
MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme.search.option_button.style_for(state, is_active);
let style = theme
.search
.option_button
.in_state(is_active)
.style_for(state);
Label::new(icon, style.text.clone())
.contained()
.with_style(style.container)
+137 -139
View File
@@ -8,6 +8,7 @@ use gpui::{
fonts::{HighlightStyle, TextStyle},
platform, AppContext, AssetSource, Border, MouseState,
};
use schemars::JsonSchema;
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value;
use settings::SettingsStore;
@@ -36,7 +37,7 @@ pub fn init(source: impl AssetSource, cx: &mut AppContext) {
.detach();
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct Theme {
#[serde(default)]
pub meta: ThemeMeta,
@@ -67,7 +68,7 @@ pub struct Theme {
pub color_scheme: ColorScheme,
}
#[derive(Deserialize, Default, Clone)]
#[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct ThemeMeta {
#[serde(skip_deserializing)]
pub id: usize,
@@ -75,7 +76,7 @@ pub struct ThemeMeta {
pub is_light: bool,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct Workspace {
pub background: Color,
pub blank_pane: BlankPaneStyle,
@@ -102,7 +103,7 @@ pub struct Workspace {
pub drop_target_overlay_color: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct BlankPaneStyle {
pub logo: SvgStyle,
pub logo_shadow: SvgStyle,
@@ -112,7 +113,7 @@ pub struct BlankPaneStyle {
pub keyboard_hint_width: f32,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Titlebar {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -128,16 +129,16 @@ pub struct Titlebar {
pub leader_avatar: AvatarStyle,
pub follower_avatar: AvatarStyle,
pub inactive_avatar_grayscale: bool,
pub sign_in_prompt: Interactive<ContainedText>,
pub sign_in_prompt: Toggleable<Interactive<ContainedText>>,
pub outdated_warning: ContainedText,
pub share_button: Interactive<ContainedText>,
pub share_button: Toggleable<Interactive<ContainedText>>,
pub call_control: Interactive<IconButton>,
pub toggle_contacts_button: Interactive<IconButton>,
pub user_menu_button: Interactive<IconButton>,
pub toggle_contacts_button: Toggleable<Interactive<IconButton>>,
pub user_menu_button: Toggleable<Interactive<IconButton>>,
pub toggle_contacts_badge: ContainerStyle,
}
#[derive(Copy, Clone, Deserialize, Default)]
#[derive(Copy, Clone, Deserialize, Default, JsonSchema)]
pub struct AvatarStyle {
#[serde(flatten)]
pub image: ImageStyle,
@@ -145,14 +146,14 @@ pub struct AvatarStyle {
pub outer_corner_radius: f32,
}
#[derive(Deserialize, Default, Clone)]
#[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct Copilot {
pub out_link_icon: Interactive<IconStyle>,
pub modal: ModalStyle,
pub auth: CopilotAuth,
}
#[derive(Deserialize, Default, Clone)]
#[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct CopilotAuth {
pub content_width: f32,
pub prompting: CopilotAuthPrompting,
@@ -162,14 +163,14 @@ pub struct CopilotAuth {
pub header: IconStyle,
}
#[derive(Deserialize, Default, Clone)]
#[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct CopilotAuthPrompting {
pub subheading: ContainedText,
pub hint: ContainedText,
pub device_code: DeviceCode,
}
#[derive(Deserialize, Default, Clone)]
#[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct DeviceCode {
pub text: TextStyle,
pub cta: ButtonStyle,
@@ -179,19 +180,19 @@ pub struct DeviceCode {
pub right_container: Interactive<ContainerStyle>,
}
#[derive(Deserialize, Default, Clone)]
#[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct CopilotAuthNotAuthorized {
pub subheading: ContainedText,
pub warning: ContainedText,
}
#[derive(Deserialize, Default, Clone)]
#[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct CopilotAuthAuthorized {
pub subheading: ContainedText,
pub hint: ContainedText,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ContactsPopover {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -199,17 +200,17 @@ pub struct ContactsPopover {
pub width: f32,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ContactList {
pub user_query_editor: FieldEditor,
pub user_query_editor_height: f32,
pub add_contact_button: IconButton,
pub header_row: Interactive<ContainedText>,
pub header_row: Toggleable<Interactive<ContainedText>>,
pub leave_call: Interactive<ContainedText>,
pub contact_row: Interactive<ContainerStyle>,
pub contact_row: Toggleable<Interactive<ContainerStyle>>,
pub row_height: f32,
pub project_row: Interactive<ProjectRow>,
pub tree_branch: Interactive<TreeBranch>,
pub project_row: Toggleable<Interactive<ProjectRow>>,
pub tree_branch: Toggleable<Interactive<TreeBranch>>,
pub contact_avatar: ImageStyle,
pub contact_status_free: ContainerStyle,
pub contact_status_busy: ContainerStyle,
@@ -221,7 +222,7 @@ pub struct ContactList {
pub calling_indicator: ContainedText,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ProjectRow {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -229,13 +230,13 @@ pub struct ProjectRow {
pub name: ContainedText,
}
#[derive(Deserialize, Default, Clone, Copy)]
#[derive(Deserialize, Default, Clone, Copy, JsonSchema)]
pub struct TreeBranch {
pub width: f32,
pub color: Color,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ContactFinder {
pub picker: Picker,
pub row_height: f32,
@@ -245,17 +246,17 @@ pub struct ContactFinder {
pub disabled_contact_button: IconButton,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct DropdownMenu {
#[serde(flatten)]
pub container: ContainerStyle,
pub header: Interactive<DropdownMenuItem>,
pub section_header: ContainedText,
pub item: Interactive<DropdownMenuItem>,
pub item: Toggleable<Interactive<DropdownMenuItem>>,
pub row_height: f32,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct DropdownMenuItem {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -266,11 +267,11 @@ pub struct DropdownMenuItem {
pub secondary_text_spacing: f32,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct TabBar {
#[serde(flatten)]
pub container: ContainerStyle,
pub pane_button: Interactive<IconButton>,
pub pane_button: Toggleable<Interactive<IconButton>>,
pub pane_button_container: ContainerStyle,
pub active_pane: TabStyles,
pub inactive_pane: TabStyles,
@@ -294,13 +295,13 @@ impl TabBar {
}
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct TabStyles {
pub active_tab: Tab,
pub inactive_tab: Tab,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct AvatarRibbon {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -308,7 +309,7 @@ pub struct AvatarRibbon {
pub height: f32,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct OfflineIcon {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -316,7 +317,7 @@ pub struct OfflineIcon {
pub color: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Tab {
pub height: f32,
#[serde(flatten)]
@@ -333,7 +334,7 @@ pub struct Tab {
pub icon_conflict: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Toolbar {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -342,14 +343,14 @@ pub struct Toolbar {
pub nav_button: Interactive<IconButton>,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Notifications {
#[serde(flatten)]
pub container: ContainerStyle,
pub width: f32,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Search {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -359,14 +360,14 @@ pub struct Search {
pub include_exclude_editor: FindEditor,
pub invalid_include_exclude_editor: ContainerStyle,
pub include_exclude_inputs: ContainedText,
pub option_button: Interactive<ContainedText>,
pub option_button: Toggleable<Interactive<ContainedText>>,
pub match_background: Color,
pub match_index: ContainedText,
pub results_status: TextStyle,
pub dismiss_button: Interactive<IconButton>,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct FindEditor {
#[serde(flatten)]
pub input: FieldEditor,
@@ -374,7 +375,7 @@ pub struct FindEditor {
pub max_width: f32,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct StatusBar {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -390,15 +391,15 @@ pub struct StatusBar {
pub diagnostic_message: Interactive<ContainedText>,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct StatusBarPanelButtons {
pub group_left: ContainerStyle,
pub group_bottom: ContainerStyle,
pub group_right: ContainerStyle,
pub button: Interactive<PanelButton>,
pub button: Toggleable<Interactive<PanelButton>>,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct StatusBarDiagnosticSummary {
pub container_ok: ContainerStyle,
pub container_warning: ContainerStyle,
@@ -413,7 +414,7 @@ pub struct StatusBarDiagnosticSummary {
pub summary_spacing: f32,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct StatusBarLspStatus {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -424,14 +425,14 @@ pub struct StatusBarLspStatus {
pub message: TextStyle,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct Dock {
pub left: ContainerStyle,
pub bottom: ContainerStyle,
pub right: ContainerStyle,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct PanelButton {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -440,20 +441,20 @@ pub struct PanelButton {
pub label: ContainedText,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ProjectPanel {
#[serde(flatten)]
pub container: ContainerStyle,
pub entry: Interactive<ProjectPanelEntry>,
pub entry: Toggleable<Interactive<ProjectPanelEntry>>,
pub dragged_entry: ProjectPanelEntry,
pub ignored_entry: Interactive<ProjectPanelEntry>,
pub cut_entry: Interactive<ProjectPanelEntry>,
pub ignored_entry: Toggleable<Interactive<ProjectPanelEntry>>,
pub cut_entry: Toggleable<Interactive<ProjectPanelEntry>>,
pub filename_editor: FieldEditor,
pub indent_width: f32,
pub open_project_button: Interactive<ContainedText>,
}
#[derive(Clone, Debug, Deserialize, Default)]
#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
pub struct ProjectPanelEntry {
pub height: f32,
#[serde(flatten)]
@@ -465,28 +466,28 @@ pub struct ProjectPanelEntry {
pub status: EntryStatus,
}
#[derive(Clone, Debug, Deserialize, Default)]
#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
pub struct EntryStatus {
pub git: GitProjectStatus,
}
#[derive(Clone, Debug, Deserialize, Default)]
#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
pub struct GitProjectStatus {
pub modified: Color,
pub inserted: Color,
pub conflict: Color,
}
#[derive(Clone, Debug, Deserialize, Default)]
#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
pub struct ContextMenu {
#[serde(flatten)]
pub container: ContainerStyle,
pub item: Interactive<ContextMenuItem>,
pub item: Toggleable<Interactive<ContextMenuItem>>,
pub keystroke_margin: f32,
pub separator: ContainerStyle,
}
#[derive(Clone, Debug, Deserialize, Default)]
#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
pub struct ContextMenuItem {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -496,13 +497,13 @@ pub struct ContextMenuItem {
pub icon_spacing: f32,
}
#[derive(Debug, Deserialize, Default)]
#[derive(Debug, Deserialize, Default, JsonSchema)]
pub struct CommandPalette {
pub key: Interactive<ContainedLabel>,
pub key: Toggleable<ContainedLabel>,
pub keystroke_spacing: f32,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct InviteLink {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -511,7 +512,7 @@ pub struct InviteLink {
pub icon: Icon,
}
#[derive(Deserialize, Clone, Copy, Default)]
#[derive(Deserialize, Clone, Copy, Default, JsonSchema)]
pub struct Icon {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -519,7 +520,7 @@ pub struct Icon {
pub width: f32,
}
#[derive(Deserialize, Clone, Copy, Default)]
#[derive(Deserialize, Clone, Copy, Default, JsonSchema)]
pub struct IconButton {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -528,7 +529,7 @@ pub struct IconButton {
pub button_width: f32,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ChatMessage {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -537,7 +538,7 @@ pub struct ChatMessage {
pub timestamp: ContainedText,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ChannelSelect {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -549,7 +550,7 @@ pub struct ChannelSelect {
pub menu: ContainerStyle,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ChannelName {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -557,7 +558,7 @@ pub struct ChannelName {
pub name: TextStyle,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Picker {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -565,10 +566,10 @@ pub struct Picker {
pub input_editor: FieldEditor,
pub empty_input_editor: FieldEditor,
pub no_matches: ContainedLabel,
pub item: Interactive<ContainedLabel>,
pub item: Toggleable<Interactive<ContainedLabel>>,
}
#[derive(Clone, Debug, Deserialize, Default)]
#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
pub struct ContainedText {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -576,7 +577,7 @@ pub struct ContainedText {
pub text: TextStyle,
}
#[derive(Clone, Debug, Deserialize, Default)]
#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
pub struct ContainedLabel {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -584,7 +585,7 @@ pub struct ContainedLabel {
pub label: LabelStyle,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct ProjectDiagnostics {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -594,7 +595,7 @@ pub struct ProjectDiagnostics {
pub tab_summary_spacing: f32,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ContactNotification {
pub header_avatar: ImageStyle,
pub header_message: ContainedText,
@@ -604,21 +605,21 @@ pub struct ContactNotification {
pub dismiss_button: Interactive<IconButton>,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct UpdateNotification {
pub message: ContainedText,
pub action_message: Interactive<ContainedText>,
pub dismiss_button: Interactive<IconButton>,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct MessageNotification {
pub message: ContainedText,
pub action_message: Interactive<ContainedText>,
pub dismiss_button: Interactive<IconButton>,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct ProjectSharedNotification {
pub window_height: f32,
pub window_width: f32,
@@ -635,7 +636,7 @@ pub struct ProjectSharedNotification {
pub dismiss_button: ContainedText,
}
#[derive(Deserialize, Default)]
#[derive(Deserialize, Default, JsonSchema)]
pub struct IncomingCallNotification {
pub window_height: f32,
pub window_width: f32,
@@ -652,7 +653,7 @@ pub struct IncomingCallNotification {
pub decline_button: ContainedText,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Editor {
pub text_color: Color,
#[serde(default)]
@@ -693,7 +694,7 @@ pub struct Editor {
pub whitespace: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Scrollbar {
pub track: ContainerStyle,
pub thumb: ContainerStyle,
@@ -702,14 +703,14 @@ pub struct Scrollbar {
pub git: GitDiffColors,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct GitDiffColors {
pub inserted: Color,
pub modified: Color,
pub deleted: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct DiagnosticPathHeader {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -718,7 +719,7 @@ pub struct DiagnosticPathHeader {
pub text_scale_factor: f32,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct DiagnosticHeader {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -729,7 +730,7 @@ pub struct DiagnosticHeader {
pub icon_width_factor: f32,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct DiagnosticStyle {
pub message: LabelStyle,
#[serde(default)]
@@ -737,7 +738,7 @@ pub struct DiagnosticStyle {
pub text_scale_factor: f32,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct AutocompleteStyle {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -747,13 +748,13 @@ pub struct AutocompleteStyle {
pub match_highlight: HighlightStyle,
}
#[derive(Clone, Copy, Default, Deserialize)]
#[derive(Clone, Copy, Default, Deserialize, JsonSchema)]
pub struct SelectionStyle {
pub cursor: Color,
pub selection: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct FieldEditor {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -763,21 +764,21 @@ pub struct FieldEditor {
pub selection: SelectionStyle,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct InteractiveColor {
pub color: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct CodeActions {
#[serde(default)]
pub indicator: Interactive<InteractiveColor>,
pub indicator: Toggleable<Interactive<InteractiveColor>>,
pub vertical_scale: f32,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Folds {
pub indicator: Interactive<InteractiveColor>,
pub indicator: Toggleable<Interactive<InteractiveColor>>,
pub ellipses: FoldEllipses,
pub fold_background: Color,
pub icon_margin_scale: f32,
@@ -785,14 +786,14 @@ pub struct Folds {
pub foldable_icon: String,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct FoldEllipses {
pub text_color: Color,
pub background: Interactive<InteractiveColor>,
pub corner_radius_factor: f32,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct DiffStyle {
pub inserted: Color,
pub modified: Color,
@@ -802,41 +803,49 @@ pub struct DiffStyle {
pub corner_radius: f32,
}
#[derive(Debug, Default, Clone, Copy)]
#[derive(Debug, Default, Clone, Copy, JsonSchema)]
pub struct Interactive<T> {
pub default: T,
pub hover: Option<T>,
pub hover_and_active: Option<T>,
pub hovered: Option<T>,
pub clicked: Option<T>,
pub click_and_active: Option<T>,
pub active: Option<T>,
pub disabled: Option<T>,
}
impl<T> Interactive<T> {
pub fn style_for(&self, state: &mut MouseState, active: bool) -> &T {
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
pub struct Toggleable<T> {
active: T,
inactive: T,
}
impl<T> Toggleable<T> {
pub fn new(active: T, inactive: T) -> Self {
Self { active, inactive }
}
pub fn in_state(&self, active: bool) -> &T {
if active {
if state.hovered() {
self.hover_and_active
.as_ref()
.unwrap_or(self.active.as_ref().unwrap_or(&self.default))
} else if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some()
{
self.click_and_active
.as_ref()
.unwrap_or(self.active.as_ref().unwrap_or(&self.default))
} else {
self.active.as_ref().unwrap_or(&self.default)
}
} else if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some() {
&self.active
} else {
&self.inactive
}
}
pub fn active_state(&self) -> &T {
self.in_state(true)
}
pub fn inactive_state(&self) -> &T {
self.in_state(false)
}
}
impl<T> Interactive<T> {
pub fn style_for(&self, state: &mut MouseState) -> &T {
if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some() {
self.clicked.as_ref().unwrap()
} else if state.hovered() {
self.hover.as_ref().unwrap_or(&self.default)
self.hovered.as_ref().unwrap_or(&self.default)
} else {
&self.default
}
}
pub fn disabled_style(&self) -> &T {
self.disabled.as_ref().unwrap_or(&self.default)
}
@@ -849,13 +858,9 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
{
#[derive(Deserialize)]
struct Helper {
#[serde(flatten)]
default: Value,
hover: Option<Value>,
hover_and_active: Option<Value>,
hovered: Option<Value>,
clicked: Option<Value>,
click_and_active: Option<Value>,
active: Option<Value>,
disabled: Option<Value>,
}
@@ -880,21 +885,15 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
}
};
let hover = deserialize_state(json.hover)?;
let hover_and_active = deserialize_state(json.hover_and_active)?;
let hovered = deserialize_state(json.hovered)?;
let clicked = deserialize_state(json.clicked)?;
let click_and_active = deserialize_state(json.click_and_active)?;
let active = deserialize_state(json.active)?;
let disabled = deserialize_state(json.disabled)?;
let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
Ok(Interactive {
default,
hover,
hover_and_active,
hovered,
clicked,
click_and_active,
active,
disabled,
})
}
@@ -911,7 +910,7 @@ impl Editor {
}
}
#[derive(Default)]
#[derive(Default, JsonSchema)]
pub struct SyntaxTheme {
pub highlights: Vec<(String, HighlightStyle)>,
}
@@ -945,7 +944,7 @@ impl<'de> Deserialize<'de> for SyntaxTheme {
}
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct HoverPopover {
pub container: ContainerStyle,
pub info_container: ContainerStyle,
@@ -957,7 +956,7 @@ pub struct HoverPopover {
pub highlight: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct TerminalStyle {
pub black: Color,
pub red: Color,
@@ -991,7 +990,7 @@ pub struct TerminalStyle {
pub dim_foreground: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct AssistantStyle {
pub container: ContainerStyle,
pub hamburger_button: IconStyle,
@@ -1014,15 +1013,14 @@ pub struct AssistantStyle {
pub saved_conversation: SavedConversation,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct SavedConversation {
#[serde(flatten)]
pub container: Interactive<ContainerStyle>,
pub saved_at: ContainedText,
pub title: ContainedText,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct FeedbackStyle {
pub submit_button: Interactive<ContainedText>,
pub button_margin: f32,
@@ -1031,7 +1029,7 @@ pub struct FeedbackStyle {
pub link_text_hover: ContainedText,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct WelcomeStyle {
pub page_width: f32,
pub logo: SvgStyle,
@@ -1045,7 +1043,7 @@ pub struct WelcomeStyle {
pub checkbox_group: ContainerStyle,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct ColorScheme {
pub name: String,
pub is_light: bool,
@@ -1060,13 +1058,13 @@ pub struct ColorScheme {
pub players: Vec<Player>,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Player {
pub cursor: Color,
pub selection: Color,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct RampSet {
pub neutral: Vec<Color>,
pub red: Vec<Color>,
@@ -1079,7 +1077,7 @@ pub struct RampSet {
pub magenta: Vec<Color>,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Layer {
pub base: StyleSet,
pub variant: StyleSet,
@@ -1090,7 +1088,7 @@ pub struct Layer {
pub negative: StyleSet,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct StyleSet {
pub default: Style,
pub active: Style,
@@ -1100,7 +1098,7 @@ pub struct StyleSet {
pub inverted: Style,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct Style {
pub background: Color,
pub border: Color,
+2 -1
View File
@@ -14,12 +14,13 @@ use util::ResultExt as _;
const MIN_FONT_SIZE: f32 = 6.0;
#[derive(Clone)]
#[derive(Clone, JsonSchema)]
pub struct ThemeSettings {
pub buffer_font_family_name: String,
pub buffer_font_features: fonts::Features,
pub buffer_font_family: FamilyId,
pub(crate) buffer_font_size: f32,
#[serde(skip)]
pub theme: Arc<Theme>,
}
+7 -6
View File
@@ -12,11 +12,12 @@ use gpui::{
scene::MouseClick,
Action, Element, EventContext, MouseState, View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{ContainedText, Interactive};
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct CheckboxStyle {
pub icon: SvgStyle,
pub label: ContainedText,
@@ -100,7 +101,7 @@ pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
.with_height(style.dimensions.height)
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct IconStyle {
pub icon: SvgStyle,
pub container: ContainerStyle,
@@ -150,7 +151,7 @@ where
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
{
MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
let style = style.style_for(state, false);
let style = style.style_for(state);
Label::new(label, style.text.to_owned())
.aligned()
.contained()
@@ -162,7 +163,7 @@ where
.with_cursor_style(platform::CursorStyle::PointingHand)
}
#[derive(Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct ModalStyle {
close_icon: Interactive<IconStyle>,
container: ContainerStyle,
@@ -200,13 +201,13 @@ where
title,
style
.title_text
.style_for(&mut MouseState::default(), false)
.style_for(&mut MouseState::default())
.clone(),
))
.with_child(
// FIXME: Get a better tag type
MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
let style = style.close_icon.style_for(state, false);
let style = style.close_icon.style_for(state);
icon(style)
})
.on_click(platform::MouseButton::Left, move |_, _, cx| {
+1 -1
View File
@@ -208,7 +208,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
cx: &AppContext,
) -> AnyElement<Picker<Self>> {
let theme = theme::current(cx);
let style = theme.picker.item.style_for(mouse_state, selected);
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
let theme_match = &self.matches[ix];
Label::new(theme_match.string.clone(), style.label.clone())
-19
View File
@@ -1,19 +0,0 @@
[package]
name = "theme_testbench"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/theme_testbench.rs"
doctest = false
[dependencies]
gpui = { path = "../gpui" }
theme = { path = "../theme" }
settings = { path = "../settings" }
workspace = { path = "../workspace" }
project = { path = "../project" }
smallvec.workspace = true
@@ -1,300 +0,0 @@
use gpui::{
actions,
color::Color,
elements::{
AnyElement, Canvas, Container, ContainerStyle, Flex, Label, Margin, MouseEventHandler,
Padding, ParentElement,
},
fonts::TextStyle,
AppContext, Border, Element, Entity, ModelHandle, Quad, Task, View, ViewContext, ViewHandle,
WeakViewHandle,
};
use project::Project;
use theme::{ColorScheme, Layer, Style, StyleSet, ThemeSettings};
use workspace::{item::Item, register_deserializable_item, Pane, Workspace};
actions!(theme, [DeployThemeTestbench]);
pub fn init(cx: &mut AppContext) {
cx.add_action(ThemeTestbench::deploy);
register_deserializable_item::<ThemeTestbench>(cx)
}
pub struct ThemeTestbench {}
impl ThemeTestbench {
pub fn deploy(
workspace: &mut Workspace,
_: &DeployThemeTestbench,
cx: &mut ViewContext<Workspace>,
) {
let view = cx.add_view(|_| ThemeTestbench {});
workspace.add_item(Box::new(view), cx);
}
fn render_ramps(color_scheme: &ColorScheme) -> Flex<Self> {
fn display_ramp(ramp: &Vec<Color>) -> AnyElement<ThemeTestbench> {
Flex::row()
.with_children(ramp.iter().cloned().map(|color| {
Canvas::new(move |scene, bounds, _, _, _| {
scene.push_quad(Quad {
bounds,
background: Some(color),
..Default::default()
});
})
.flex(1.0, false)
}))
.flex(1.0, false)
.into_any()
}
Flex::column()
.with_child(display_ramp(&color_scheme.ramps.neutral))
.with_child(display_ramp(&color_scheme.ramps.red))
.with_child(display_ramp(&color_scheme.ramps.orange))
.with_child(display_ramp(&color_scheme.ramps.yellow))
.with_child(display_ramp(&color_scheme.ramps.green))
.with_child(display_ramp(&color_scheme.ramps.cyan))
.with_child(display_ramp(&color_scheme.ramps.blue))
.with_child(display_ramp(&color_scheme.ramps.violet))
.with_child(display_ramp(&color_scheme.ramps.magenta))
}
fn render_layer(
layer_index: usize,
layer: &Layer,
cx: &mut ViewContext<Self>,
) -> Container<Self> {
Flex::column()
.with_child(
Self::render_button_set(0, layer_index, "base", &layer.base, cx).flex(1., false),
)
.with_child(
Self::render_button_set(1, layer_index, "variant", &layer.variant, cx)
.flex(1., false),
)
.with_child(
Self::render_button_set(2, layer_index, "on", &layer.on, cx).flex(1., false),
)
.with_child(
Self::render_button_set(3, layer_index, "accent", &layer.accent, cx)
.flex(1., false),
)
.with_child(
Self::render_button_set(4, layer_index, "positive", &layer.positive, cx)
.flex(1., false),
)
.with_child(
Self::render_button_set(5, layer_index, "warning", &layer.warning, cx)
.flex(1., false),
)
.with_child(
Self::render_button_set(6, layer_index, "negative", &layer.negative, cx)
.flex(1., false),
)
.contained()
.with_style(ContainerStyle {
margin: Margin {
top: 10.,
bottom: 10.,
left: 10.,
right: 10.,
},
background_color: Some(layer.base.default.background),
..Default::default()
})
}
fn render_button_set(
set_index: usize,
layer_index: usize,
set_name: &'static str,
style_set: &StyleSet,
cx: &mut ViewContext<Self>,
) -> Flex<Self> {
Flex::row()
.with_child(Self::render_button(
set_index * 6,
layer_index,
set_name,
&style_set,
None,
cx,
))
.with_child(Self::render_button(
set_index * 6 + 1,
layer_index,
"hovered",
&style_set,
Some(|style_set| &style_set.hovered),
cx,
))
.with_child(Self::render_button(
set_index * 6 + 2,
layer_index,
"pressed",
&style_set,
Some(|style_set| &style_set.pressed),
cx,
))
.with_child(Self::render_button(
set_index * 6 + 3,
layer_index,
"active",
&style_set,
Some(|style_set| &style_set.active),
cx,
))
.with_child(Self::render_button(
set_index * 6 + 4,
layer_index,
"disabled",
&style_set,
Some(|style_set| &style_set.disabled),
cx,
))
.with_child(Self::render_button(
set_index * 6 + 5,
layer_index,
"inverted",
&style_set,
Some(|style_set| &style_set.inverted),
cx,
))
}
fn render_button(
button_index: usize,
layer_index: usize,
text: &'static str,
style_set: &StyleSet,
style_override: Option<fn(&StyleSet) -> &Style>,
cx: &mut ViewContext<Self>,
) -> AnyElement<Self> {
enum TestBenchButton {}
MouseEventHandler::<TestBenchButton, _>::new(layer_index + button_index, cx, |state, cx| {
let style = if let Some(style_override) = style_override {
style_override(&style_set)
} else if state.clicked().is_some() {
&style_set.pressed
} else if state.hovered() {
&style_set.hovered
} else {
&style_set.default
};
Self::render_label(text.to_string(), style, cx)
.contained()
.with_style(ContainerStyle {
margin: Margin {
top: 4.,
bottom: 4.,
left: 4.,
right: 4.,
},
padding: Padding {
top: 4.,
bottom: 4.,
left: 4.,
right: 4.,
},
background_color: Some(style.background),
border: Border {
width: 1.,
color: style.border,
overlay: false,
top: true,
bottom: true,
left: true,
right: true,
},
corner_radius: 2.,
..Default::default()
})
})
.flex(1., true)
.into_any()
}
fn render_label(text: String, style: &Style, cx: &mut ViewContext<Self>) -> Label {
let settings = settings::get::<ThemeSettings>(cx);
let font_cache = cx.font_cache();
let family_id = settings.buffer_font_family;
let font_size = settings.buffer_font_size(cx);
let font_id = font_cache
.select_font(family_id, &Default::default())
.unwrap();
let text_style = TextStyle {
color: style.foreground,
font_family_id: family_id,
font_family_name: font_cache.family_name(family_id).unwrap(),
font_id,
font_size,
font_properties: Default::default(),
underline: Default::default(),
};
Label::new(text, text_style)
}
}
impl Entity for ThemeTestbench {
type Event = ();
}
impl View for ThemeTestbench {
fn ui_name() -> &'static str {
"ThemeTestbench"
}
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<Self> {
let color_scheme = &theme::current(cx).clone().color_scheme;
Flex::row()
.with_child(
Self::render_ramps(color_scheme)
.contained()
.with_margin_right(10.)
.flex(0.1, false),
)
.with_child(
Flex::column()
.with_child(Self::render_layer(100, &color_scheme.lowest, cx).flex(1., true))
.with_child(Self::render_layer(200, &color_scheme.middle, cx).flex(1., true))
.with_child(Self::render_layer(300, &color_scheme.highest, cx).flex(1., true))
.flex(1., false),
)
.into_any()
}
}
impl Item for ThemeTestbench {
fn tab_content<T: View>(
&self,
_: Option<usize>,
style: &theme::Tab,
_: &AppContext,
) -> AnyElement<T> {
Label::new("Theme Testbench", style.label.clone())
.aligned()
.contained()
.into_any()
}
fn serialized_item_kind() -> Option<&'static str> {
Some("ThemeTestBench")
}
fn deserialize(
_project: ModelHandle<Project>,
_workspace: WeakViewHandle<Workspace>,
_workspace_id: workspace::WorkspaceId,
_item_id: workspace::ItemId,
cx: &mut ViewContext<Pane>,
) -> Task<gpui::anyhow::Result<ViewHandle<Self>>> {
Task::ready(Ok(cx.add_view(|_| Self {})))
}
}
+11
View File
@@ -98,3 +98,14 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
assert_eq!(bar.query_editor.read(cx).text(cx), "jumps");
})
}
#[gpui::test]
async fn test_count_down(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
cx.simulate_keystrokes(["2", "down"]);
cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
cx.simulate_keystrokes(["9", "down"]);
cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
}
+1 -1
View File
@@ -141,7 +141,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
) -> gpui::AnyElement<Picker<Self>> {
let theme = &theme::current(cx);
let keymap_match = &self.matches[ix];
let style = theme.picker.item.style_for(mouse_state, selected);
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
Label::new(keymap_match.string.clone(), style.label.clone())
.with_highlights(keymap_match.positions.clone())
+3 -1
View File
@@ -498,7 +498,9 @@ impl View for PanelButtons {
Stack::new()
.with_child(
MouseEventHandler::<Self, _>::new(panel_ix, cx, |state, cx| {
let style = button_style.style_for(state, is_active);
let style = button_style.in_state(is_active);
let style = style.style_for(state);
Flex::row()
.with_child(
Svg::new(view.icon_path(cx))
+2 -2
View File
@@ -291,7 +291,7 @@ pub mod simple_message_notification {
)
.with_child(
MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false);
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
.constrained()
@@ -323,7 +323,7 @@ pub mod simple_message_notification {
0,
cx,
|state, _| {
let style = theme.action_message.style_for(state, false);
let style = theme.action_message.style_for(state);
Flex::row()
.with_child(
+2 -2
View File
@@ -1410,7 +1410,7 @@ impl Pane {
pub fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>(
index: usize,
icon: &'static str,
active: bool,
is_active: bool,
tooltip: Option<(String, Option<Box<dyn Action>>)>,
cx: &mut ViewContext<Pane>,
on_click: F,
@@ -1420,7 +1420,7 @@ impl Pane {
let mut button = MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
let style = theme.pane_button.style_for(mouse_state, active);
let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
Svg::new(icon)
.with_color(style.color)
.constrained()
+1 -1
View File
@@ -231,7 +231,7 @@ fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>
) -> AnyElement<Toolbar> {
MouseEventHandler::<A, _>::new(0, cx, |state, _| {
let style = if enabled {
style.style_for(state, false)
style.style_for(state)
} else {
style.disabled_style()
};
+4 -2
View File
@@ -140,9 +140,11 @@ pub struct OpenPaths {
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivatePane(pub usize);
#[derive(Deserialize)]
pub struct Toast {
id: usize,
msg: Cow<'static, str>,
#[serde(skip)]
on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
}
@@ -183,9 +185,9 @@ impl Clone for Toast {
}
}
pub type WorkspaceId = i64;
impl_actions!(workspace, [ActivatePane, Toast]);
impl_actions!(workspace, [ActivatePane]);
pub type WorkspaceId = i64;
pub fn init_settings(cx: &mut AppContext) {
settings::register::<WorkspaceSettings>(cx);
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "xtask"
version = "0.1.0"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"
clap = {version = "4.0", features = ["derive"]}
theme = {path = "../theme"}
serde_json.workspace = true
schemars.workspace = true
+23
View File
@@ -0,0 +1,23 @@
use clap::{Parser, Subcommand};
use std::path::PathBuf;
/// Common utilities for Zed developers.
// For more information, see [matklad's repository README](https://github.com/matklad/cargo-xtask/)
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
/// Command to run.
#[derive(Subcommand)]
pub enum Commands {
/// Builds theme types for interop with Typescript.
BuildThemeTypes {
#[clap(short, long, default_value = "schemas")]
out_dir: PathBuf,
#[clap(short, long, default_value = "theme.json")]
file_name: PathBuf,
},
}
+29
View File
@@ -0,0 +1,29 @@
mod cli;
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use schemars::schema_for;
use theme::Theme;
fn build_themes(out_dir: PathBuf, file_name: PathBuf) -> Result<()> {
let theme = schema_for!(Theme);
let output = serde_json::to_string_pretty(&theme)?;
std::fs::create_dir(&out_dir)?;
let mut file_path = out_dir;
file_path.push(file_name);
std::fs::write(file_path, output)?;
Ok(())
}
fn main() -> Result<()> {
let args = cli::Cli::parse();
match args.command {
cli::Commands::BuildThemeTypes { out_dir, file_name } => build_themes(out_dir, file_name),
}
}
+1 -2
View File
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.92.0"
version = "0.93.0"
publish = false
[lib]
@@ -62,7 +62,6 @@ text = { path = "../text" }
terminal_view = { path = "../terminal_view" }
theme = { path = "../theme" }
theme_selector = { path = "../theme_selector" }
theme_testbench = { path = "../theme_testbench" }
util = { path = "../util" }
vim = { path = "../vim" }
workspace = { path = "../workspace" }
-1
View File
@@ -154,7 +154,6 @@ fn main() {
search::init(cx);
vim::init(cx);
terminal_view::init(cx);
theme_testbench::init(cx);
copilot::init(http.clone(), node_runtime, cx);
ai::init(cx);