Detailed changes
@@ -318,7 +318,7 @@ impl View for ActivityIndicator {
on_click,
} = self.content_to_render(cx);
- let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
+ let mut element = MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
let theme = &theme::current(cx).workspace.status_bar.lsp_status;
let style = if state.hovered() && on_click.is_some() {
theme.hovered.as_ref().unwrap_or(&theme.default)
@@ -349,7 +349,7 @@ impl AssistantPanel {
enum History {}
let theme = theme::current(cx);
let tooltip_style = theme::current(cx).tooltip.clone();
- MouseEventHandler::<History, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<History, _>(0, cx, |state, _| {
let style = theme.assistant.hamburger_button.style_for(state);
Svg::for_style(style.icon.clone())
.contained()
@@ -381,7 +381,7 @@ impl AssistantPanel {
fn render_split_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
let theme = theme::current(cx);
let tooltip_style = theme::current(cx).tooltip.clone();
- MouseEventHandler::<Split, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<Split, _>(0, cx, |state, _| {
let style = theme.assistant.split_button.style_for(state);
Svg::for_style(style.icon.clone())
.contained()
@@ -405,7 +405,7 @@ impl AssistantPanel {
fn render_assist_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
let theme = theme::current(cx);
let tooltip_style = theme::current(cx).tooltip.clone();
- MouseEventHandler::<Assist, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<Assist, _>(0, cx, |state, _| {
let style = theme.assistant.assist_button.style_for(state);
Svg::for_style(style.icon.clone())
.contained()
@@ -423,7 +423,7 @@ impl AssistantPanel {
fn render_quote_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
let theme = theme::current(cx);
let tooltip_style = theme::current(cx).tooltip.clone();
- MouseEventHandler::<QuoteSelection, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<QuoteSelection, _>(0, cx, |state, _| {
let style = theme.assistant.quote_button.style_for(state);
Svg::for_style(style.icon.clone())
.contained()
@@ -451,7 +451,7 @@ impl AssistantPanel {
fn render_plus_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
let theme = theme::current(cx);
let tooltip_style = theme::current(cx).tooltip.clone();
- MouseEventHandler::<NewConversation, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<NewConversation, _>(0, cx, |state, _| {
let style = theme.assistant.plus_button.style_for(state);
Svg::for_style(style.icon.clone())
.contained()
@@ -481,7 +481,7 @@ impl AssistantPanel {
&theme.assistant.zoom_in_button
};
- MouseEventHandler::<ToggleZoomButton, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<ToggleZoomButton, _>(0, cx, |state, _| {
let style = style.style_for(state);
Svg::for_style(style.icon.clone())
.contained()
@@ -507,7 +507,7 @@ impl AssistantPanel {
) -> impl Element<Self> {
let conversation = &self.saved_conversations[index];
let path = conversation.path.clone();
- MouseEventHandler::<SavedConversationMetadata, _>::new(index, cx, move |state, cx| {
+ MouseEventHandler::new::<SavedConversationMetadata, _>(index, cx, move |state, cx| {
let style = &theme::current(cx).assistant.saved_conversation;
Flex::row()
.with_child(
@@ -1821,7 +1821,7 @@ impl ConversationEditor {
let theme = theme::current(cx);
let style = &theme.assistant;
let message_id = message.id;
- let sender = MouseEventHandler::<Sender, _>::new(
+ let sender = MouseEventHandler::new::<Sender, _>(
message_id.0,
cx,
|state, _| match message.role {
@@ -2047,7 +2047,7 @@ impl ConversationEditor {
) -> impl Element<Self> {
enum Model {}
- MouseEventHandler::<Model, _>::new(0, cx, |state, cx| {
+ MouseEventHandler::new::<Model, _>(0, cx, |state, cx| {
let style = style.model.style_for(state);
Label::new(self.conversation.read(cx).model.clone(), style.text.clone())
.contained()
@@ -31,7 +31,7 @@ impl View for UpdateNotification {
let app_name = cx.global::<ReleaseChannel>().display_name();
- MouseEventHandler::<ViewReleaseNotes, _>::new(0, cx, |state, cx| {
+ MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
Flex::column()
.with_child(
Flex::row()
@@ -48,7 +48,7 @@ impl View for UpdateNotification {
.flex(1., true),
)
.with_child(
- MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
@@ -82,7 +82,7 @@ impl View for Breadcrumbs {
.into_any();
}
- MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
let style = style.style_for(state);
crumbs.with_style(style.container)
})
@@ -7952,7 +7952,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ inlay_cache.version(),
+ edits_made,
"Host editor update the cache version after every cache/view change",
);
});
@@ -7975,7 +7976,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ inlay_cache.version(),
+ edits_made,
"Guest editor update the cache version after every cache/view change"
);
});
@@ -7995,7 +7997,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
"Host should get hints from the 1st edit and 1st LSP query"
);
let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, edits_made);
+ assert_eq!(inlay_cache.version(), edits_made);
});
editor_b.update(cx_b, |editor, _| {
assert_eq!(
@@ -8009,7 +8011,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
"Guest should get hints the 1st edit and 2nd LSP query"
);
let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, edits_made);
+ assert_eq!(inlay_cache.version(), edits_made);
});
editor_a.update(cx_a, |editor, cx| {
@@ -8034,7 +8036,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
4th query was made by guest (but not applied) due to cache invalidation logic"
);
let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, edits_made);
+ assert_eq!(inlay_cache.version(), edits_made);
});
editor_b.update(cx_b, |editor, _| {
assert_eq!(
@@ -8050,7 +8052,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
"Guest should get hints from 3rd edit, 6th LSP query"
);
let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, edits_made);
+ assert_eq!(inlay_cache.version(), edits_made);
});
fake_language_server
@@ -8076,7 +8078,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ inlay_cache.version(),
+ edits_made,
"Host should accepted all edits and bump its cache version every time"
);
});
@@ -8097,7 +8100,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version,
+ inlay_cache.version(),
edits_made,
"Guest should accepted all edits and bump its cache version every time"
);
@@ -8263,7 +8266,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 0,
+ inlay_cache.version(),
+ 0,
"Host should not increment its cache version due to no changes",
);
});
@@ -8278,7 +8282,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ inlay_cache.version(),
+ edits_made,
"Guest editor update the cache version after every cache/view change"
);
});
@@ -8295,7 +8300,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 0,
+ inlay_cache.version(),
+ 0,
"Host should not increment its cache version due to no changes",
);
});
@@ -8310,7 +8316,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
);
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ inlay_cache.version(),
+ edits_made,
"Guest should accepted all edits and bump its cache version every time"
);
});
@@ -8318,13 +8325,10 @@ async fn test_inlay_hint_refresh_is_forwarded(
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
let mut labels = Vec::new();
- for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
- let excerpt_hints = excerpt_hints.read();
- for (_, inlay) in excerpt_hints.hints.iter() {
- match &inlay.label {
- project::InlayHintLabel::String(s) => labels.push(s.to_string()),
- _ => unreachable!(),
- }
+ for hint in editor.inlay_hint_cache().hints() {
+ match hint.label {
+ project::InlayHintLabel::String(s) => labels.push(s),
+ _ => unreachable!(),
}
}
labels
@@ -981,7 +981,7 @@ impl CollabPanel {
worktree_root_names.join(", ")
};
- MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
+ MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, _| {
let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
let row = theme
.project_row
@@ -1075,7 +1075,7 @@ impl CollabPanel {
let baseline_offset =
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
- MouseEventHandler::<OpenSharedScreen, Self>::new(
+ MouseEventHandler::new::<OpenSharedScreen, _>(
peer_id.as_u64() as usize,
cx,
|mouse_state, _| {
@@ -1193,7 +1193,7 @@ impl CollabPanel {
enum AddContact {}
let button = match section {
Section::ActiveCall => Some(
- MouseEventHandler::<AddContact, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<AddContact, _>(0, cx, |state, _| {
render_icon_button(
theme
.collab_panel
@@ -1215,7 +1215,7 @@ impl CollabPanel {
),
),
Section::Contacts => Some(
- MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| {
render_icon_button(
theme
.collab_panel
@@ -1237,7 +1237,7 @@ impl CollabPanel {
),
),
Section::Channels => Some(
- MouseEventHandler::<AddChannel, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<AddChannel, _>(0, cx, |state, _| {
render_icon_button(
theme
.collab_panel
@@ -1261,7 +1261,7 @@ impl CollabPanel {
let can_collapse = depth > 0;
let icon_size = (&theme.collab_panel).section_icon_size;
- MouseEventHandler::<Header, Self>::new(section as usize, cx, |state, _| {
+ MouseEventHandler::new::<Header, _>(section as usize, cx, |state, _| {
let header_style = if can_collapse {
theme
.collab_panel
@@ -1330,7 +1330,7 @@ impl CollabPanel {
let github_login = contact.user.github_login.clone();
let initial_project = project.clone();
let mut event_handler =
- MouseEventHandler::<Contact, Self>::new(contact.user.id as usize, cx, |state, cx| {
+ MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| {
Flex::row()
.with_children(contact.user.avatar.clone().map(|avatar| {
let status_badge = if contact.online {
@@ -1369,7 +1369,7 @@ impl CollabPanel {
.flex(1., true),
)
.with_child(
- MouseEventHandler::<Cancel, Self>::new(
+ MouseEventHandler::new::<Cancel, _>(
contact.user.id as usize,
cx,
|mouse_state, _| {
@@ -1421,7 +1421,7 @@ impl CollabPanel {
cx: &mut ViewContext<Self>,
) -> AnyElement<Self> {
enum AddContacts {}
- MouseEventHandler::<AddContacts, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<AddContacts, _>(0, cx, |state, _| {
let style = theme.list_empty_state.style_for(is_selected, state);
Flex::row()
.with_child(
@@ -1516,7 +1516,7 @@ impl CollabPanel {
const FACEPILE_LIMIT: usize = 4;
- MouseEventHandler::<Channel, Self>::new(channel.id as usize, cx, |state, cx| {
+ MouseEventHandler::new::<Channel, _>(channel.id as usize, cx, |state, cx| {
Flex::row()
.with_child(
Svg::new("icons/hash.svg")
@@ -1618,18 +1618,14 @@ impl CollabPanel {
.flex(1., true),
)
.with_child(
- MouseEventHandler::<Decline, Self>::new(
- channel.id as usize,
- cx,
- |mouse_state, _| {
- let button_style = if is_invite_pending {
- &theme.disabled_button
- } else {
- theme.contact_button.style_for(mouse_state)
- };
- render_icon_button(button_style, "icons/x.svg").aligned()
- },
- )
+ MouseEventHandler::new::<Decline, _>(channel.id as usize, cx, |mouse_state, _| {
+ let button_style = if is_invite_pending {
+ &theme.disabled_button
+ } else {
+ theme.contact_button.style_for(mouse_state)
+ };
+ render_icon_button(button_style, "icons/x.svg").aligned()
+ })
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, this, cx| {
this.respond_to_channel_invite(channel_id, false, cx);
@@ -1638,20 +1634,16 @@ impl CollabPanel {
.with_margin_right(button_spacing),
)
.with_child(
- MouseEventHandler::<Accept, Self>::new(
- channel.id as usize,
- cx,
- |mouse_state, _| {
- let button_style = if is_invite_pending {
- &theme.disabled_button
- } else {
- theme.contact_button.style_for(mouse_state)
- };
- render_icon_button(button_style, "icons/check_8.svg")
- .aligned()
- .flex_float()
- },
- )
+ MouseEventHandler::new::<Accept, _>(channel.id as usize, cx, |mouse_state, _| {
+ let button_style = if is_invite_pending {
+ &theme.disabled_button
+ } else {
+ theme.contact_button.style_for(mouse_state)
+ };
+ render_icon_button(button_style, "icons/check_8.svg")
+ .aligned()
+ .flex_float()
+ })
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, this, cx| {
this.respond_to_channel_invite(channel_id, true, cx);
@@ -1710,7 +1702,7 @@ impl CollabPanel {
if is_incoming {
row.add_child(
- MouseEventHandler::<Decline, Self>::new(user.id as usize, cx, |mouse_state, _| {
+ MouseEventHandler::new::<Decline, _>(user.id as usize, cx, |mouse_state, _| {
let button_style = if is_contact_request_pending {
&theme.disabled_button
} else {
@@ -1727,7 +1719,7 @@ impl CollabPanel {
);
row.add_child(
- MouseEventHandler::<Accept, Self>::new(user.id as usize, cx, |mouse_state, _| {
+ MouseEventHandler::new::<Accept, _>(user.id as usize, cx, |mouse_state, _| {
let button_style = if is_contact_request_pending {
&theme.disabled_button
} else {
@@ -1744,7 +1736,7 @@ impl CollabPanel {
);
} else {
row.add_child(
- MouseEventHandler::<Cancel, Self>::new(user.id as usize, cx, |mouse_state, _| {
+ MouseEventHandler::new::<Cancel, _>(user.id as usize, cx, |mouse_state, _| {
let button_style = if is_contact_request_pending {
&theme.disabled_button
} else {
@@ -2266,7 +2258,7 @@ impl View for CollabPanel {
return Flex::column()
.with_child(
- MouseEventHandler::<LogInButton, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<LogInButton, _>(0, cx, |state, _| {
let button = theme.log_in_button.style_for(state);
Label::new("Sign in to collaborate", button.text.clone())
.contained()
@@ -2287,7 +2279,7 @@ impl View for CollabPanel {
}
enum PanelFocus {}
- MouseEventHandler::<PanelFocus, _>::new(0, cx, |_, cx| {
+ MouseEventHandler::new::<PanelFocus, _>(0, cx, |_, cx| {
Stack::new()
.with_child(
Flex::column()
@@ -164,7 +164,7 @@ impl View for ChannelModal {
cx: &mut ViewContext<ChannelModal>,
) -> AnyElement<ChannelModal> {
let active = mode == current_mode;
- MouseEventHandler::<T, _>::new(0, cx, move |state, _| {
+ MouseEventHandler::new::<T, _>(0, cx, move |state, _| {
let contained_text = theme.tab_button.style_for(active, state);
Label::new(text, contained_text.text.clone())
.contained()
@@ -219,7 +219,7 @@ impl CollabTitlebarItem {
let mut ret = Flex::row().with_child(
Stack::new()
.with_child(
- MouseEventHandler::<ToggleProjectMenu, Self>::new(0, cx, |mouse_state, cx| {
+ MouseEventHandler::new::<ToggleProjectMenu, _>(0, cx, |mouse_state, cx| {
let style = project_style
.in_state(self.project_popover.is_some())
.style_for(mouse_state);
@@ -259,7 +259,7 @@ impl CollabTitlebarItem {
.with_child(
Stack::new()
.with_child(
- MouseEventHandler::<ToggleVcsMenu, Self>::new(
+ MouseEventHandler::new::<ToggleVcsMenu, _>(
0,
cx,
|mouse_state, cx| {
@@ -363,7 +363,7 @@ impl CollabTitlebarItem {
self.branch_popover.as_ref().map(|child| {
let theme = theme::current(cx).clone();
let child = ChildView::new(child, cx);
- let child = MouseEventHandler::<BranchList, Self>::new(0, cx, |_, _| {
+ let child = MouseEventHandler::new::<BranchList, _>(0, cx, |_, _| {
child
.flex(1., true)
.contained()
@@ -399,7 +399,7 @@ impl CollabTitlebarItem {
self.project_popover.as_ref().map(|child| {
let theme = theme::current(cx).clone();
let child = ChildView::new(child, cx);
- let child = MouseEventHandler::<RecentProjects, Self>::new(0, cx, |_, _| {
+ let child = MouseEventHandler::new::<RecentProjects, _>(0, cx, |_, _| {
child
.flex(1., true)
.contained()
@@ -505,7 +505,7 @@ impl CollabTitlebarItem {
let active = room.read(cx).is_screen_sharing();
let titlebar = &theme.titlebar;
- MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<ToggleScreenSharing, _>(0, cx, |state, _| {
let style = titlebar
.screen_share_button
.in_state(active)
@@ -554,7 +554,7 @@ impl CollabTitlebarItem {
}
let titlebar = &theme.titlebar;
- MouseEventHandler::<ToggleMute, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<ToggleMute, _>(0, cx, |state, _| {
let style = titlebar
.toggle_microphone_button
.in_state(is_muted)
@@ -607,7 +607,7 @@ impl CollabTitlebarItem {
}
let titlebar = &theme.titlebar;
- MouseEventHandler::<ToggleDeafen, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<ToggleDeafen, _>(0, cx, |state, _| {
let style = titlebar
.toggle_speakers_button
.in_state(is_deafened)
@@ -642,7 +642,7 @@ impl CollabTitlebarItem {
let tooltip = "Leave call";
let titlebar = &theme.titlebar;
- MouseEventHandler::<LeaveCall, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<LeaveCall, _>(0, cx, |state, _| {
let style = titlebar.leave_call_button.style_for(state);
Svg::new(icon)
.with_color(style.color)
@@ -696,7 +696,7 @@ impl CollabTitlebarItem {
Some(
Stack::new()
.with_child(
- MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<ShareUnshare, _>(0, cx, |state, _| {
//TODO: Ensure this button has consistent width for both text variations
let style = titlebar.share_button.inactive_state().style_for(state);
Label::new(label, style.text.clone())
@@ -742,7 +742,7 @@ impl CollabTitlebarItem {
let avatar_style = &user_menu_button_style.avatar;
Stack::new()
.with_child(
- MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<ToggleUserMenu, _>(0, cx, |state, _| {
let style = user_menu_button_style
.user_menu
.inactive_state()
@@ -802,7 +802,7 @@ impl CollabTitlebarItem {
fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let titlebar = &theme.titlebar;
- MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<SignIn, _>(0, cx, |state, _| {
let style = titlebar.sign_in_button.inactive_state().style_for(state);
Label::new("Sign In", style.text.clone())
.contained()
@@ -1020,7 +1020,7 @@ impl CollabTitlebarItem {
if let Some(replica_id) = replica_id {
enum ToggleFollow {}
- content = MouseEventHandler::<ToggleFollow, Self>::new(
+ content = MouseEventHandler::new::<ToggleFollow, _>(
replica_id.into(),
cx,
move |_, _| content,
@@ -1051,7 +1051,7 @@ impl CollabTitlebarItem {
enum JoinProject {}
let user_id = user.id;
- content = MouseEventHandler::<JoinProject, Self>::new(
+ content = MouseEventHandler::new::<JoinProject, _>(
peer_id.as_u64() as usize,
cx,
move |_, _| content,
@@ -1139,7 +1139,7 @@ impl CollabTitlebarItem {
.into_any(),
),
client::Status::UpgradeRequired => Some(
- MouseEventHandler::<ConnectionStatusButton, Self>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<ConnectionStatusButton, _>(0, cx, |_, _| {
Label::new(
"Please update Zed to collaborate",
theme.titlebar.outdated_warning.text.clone(),
@@ -173,7 +173,7 @@ impl IncomingCallNotification {
let theme = theme::current(cx);
Flex::column()
.with_child(
- MouseEventHandler::<Accept, Self>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
let theme = &theme.incoming_call_notification;
Label::new("Accept", theme.accept_button.text.clone())
.aligned()
@@ -187,7 +187,7 @@ impl IncomingCallNotification {
.flex(1., true),
)
.with_child(
- MouseEventHandler::<Decline, Self>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
let theme = &theme.incoming_call_notification;
Label::new("Decline", theme.decline_button.text.clone())
.aligned()
@@ -52,7 +52,7 @@ where
.flex(1., true),
)
.with_child(
- MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
+ MouseEventHandler::new::<Dismiss, _>(user.id as usize, cx, |state, _| {
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
@@ -92,7 +92,7 @@ where
Flex::row()
.with_children(buttons.into_iter().enumerate().map(
|(ix, (message, handler))| {
- MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
+ MouseEventHandler::new::<Button, _>(ix, cx, |state, _| {
let button = theme.button.style_for(state);
Label::new(message, button.text.clone())
.contained()
@@ -170,7 +170,7 @@ impl ProjectSharedNotification {
let theme = theme::current(cx);
Flex::column()
.with_child(
- MouseEventHandler::<Open, Self>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
let theme = &theme.project_shared_notification;
Label::new("Open", theme.open_button.text.clone())
.aligned()
@@ -182,7 +182,7 @@ impl ProjectSharedNotification {
.flex(1., true),
)
.with_child(
- MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
let theme = &theme.project_shared_notification;
Label::new("Dismiss", theme.dismiss_button.text.clone())
.aligned()
@@ -47,7 +47,7 @@ impl View for SharingStatusIndicator {
Appearance::Dark | Appearance::VibrantDark => Color::white(),
};
- MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<Self, _>(0, cx, |_, _| {
Svg::new("icons/disable_screen_sharing_12.svg")
.with_color(color)
.constrained()
@@ -439,14 +439,14 @@ impl ContextMenu {
let style = theme::current(cx).context_menu.clone();
- MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
+ MouseEventHandler::new::<Menu, _>(0, cx, |_, cx| {
Flex::column()
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item {
ContextMenuItem::Item { label, action } => {
let action = action.clone();
let view_id = self.parent_view_id;
- MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
+ MouseEventHandler::new::<MenuItem, _>(ix, cx, |state, _| {
let style = style.item.in_state(self.selected_index == Some(ix));
let style = style.style_for(state);
let keystroke = match &action {
@@ -113,7 +113,7 @@ impl CopilotCodeVerification {
let device_code_style = &style.auth.prompting.device_code;
- MouseEventHandler::<Self, _>::new(0, cx, |state, _cx| {
+ MouseEventHandler::new::<Self, _>(0, cx, |state, _cx| {
Flex::row()
.with_child(
Label::new(data.user_code.clone(), device_code_style.text.clone())
@@ -62,7 +62,7 @@ impl View for CopilotButton {
Stack::new()
.with_child(
- MouseEventHandler::<Self, _>::new(0, cx, {
+ MouseEventHandler::new::<Self, _>(0, cx, {
let theme = theme.clone();
let status = status.clone();
move |state, _cx| {
@@ -94,7 +94,7 @@ impl View for DiagnosticIndicator {
let tooltip_style = theme::current(cx).tooltip.clone();
let in_progress = !self.in_progress_checks.is_empty();
let mut element = Flex::row().with_child(
- MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
+ MouseEventHandler::new::<Summary, _>(0, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme
.workspace
@@ -195,7 +195,7 @@ impl View for DiagnosticIndicator {
} else if let Some(diagnostic) = &self.current_diagnostic {
let message_style = style.diagnostic_message.clone();
element.add_child(
- MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
+ MouseEventHandler::new::<Message, _>(1, cx, |state, _| {
Label::new(
diagnostic.message.split('\n').next().unwrap().to_string(),
message_style.style_for(state).text.clone(),
@@ -202,7 +202,7 @@ impl<V: View> DragAndDrop<V> {
let position = (position - region_offset).round();
Some(
Overlay::new(
- MouseEventHandler::<DraggedElementHandler, V>::new(
+ MouseEventHandler::new::<DraggedElementHandler, _>(
0,
cx,
|_, cx| render(payload, cx),
@@ -235,7 +235,7 @@ impl<V: View> DragAndDrop<V> {
}
State::Canceled => Some(
- MouseEventHandler::<DraggedElementHandler, V>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<DraggedElementHandler, _>(0, cx, |_, _| {
Empty::new().constrained().with_width(0.).with_height(0.)
})
.on_up(MouseButton::Left, |_, _, cx| {
@@ -301,7 +301,7 @@ pub trait Draggable<V: View> {
Self: Sized;
}
-impl<Tag, V: View> Draggable<V> for MouseEventHandler<Tag, V> {
+impl<V: View> Draggable<V> for MouseEventHandler<V> {
fn as_draggable<D: View, P: Any>(
self,
payload: P,
@@ -867,7 +867,7 @@ impl CompletionsMenu {
let completion = &completions[mat.candidate_id];
let item_ix = start_ix + ix;
items.push(
- MouseEventHandler::<CompletionTag, _>::new(
+ MouseEventHandler::new::<CompletionTag, _>(
mat.candidate_id,
cx,
|state, _| {
@@ -1044,7 +1044,7 @@ impl CodeActionsMenu {
for (ix, action) in actions[range].iter().enumerate() {
let item_ix = start_ix + ix;
items.push(
- MouseEventHandler::<ActionTag, _>::new(item_ix, cx, |state, _| {
+ MouseEventHandler::new::<ActionTag, _>(item_ix, cx, |state, _| {
let item_style = if item_ix == selected_item {
style.autocomplete.selected_item
} else if state.hovered() {
@@ -2723,7 +2723,7 @@ impl Editor {
.collect()
}
- fn excerpt_visible_offsets(
+ pub fn excerpt_visible_offsets(
&self,
restrict_to_languages: Option<&HashSet<Arc<Language>>>,
cx: &mut ViewContext<'_, '_, Editor>,
@@ -3547,7 +3547,7 @@ impl Editor {
if self.available_code_actions.is_some() {
enum CodeActions {}
Some(
- MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<CodeActions, _>(0, cx, |state, _| {
Svg::new("icons/bolt_8.svg").with_color(
style
.code_actions
@@ -3594,7 +3594,7 @@ impl Editor {
fold_data
.map(|(fold_status, buffer_row, active)| {
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
- MouseEventHandler::<FoldIndicators, _>::new(
+ MouseEventHandler::new::<FoldIndicators, _>(
ix as usize,
cx,
|mouse_state, _| {
@@ -8663,7 +8663,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
let anchor_x = cx.anchor_x;
enum BlockContextToolip {}
- MouseEventHandler::<BlockContext, _>::new(cx.block_id, cx, |_, _| {
+ MouseEventHandler::new::<BlockContext, _>(cx.block_id, cx, |_, _| {
Flex::column()
.with_children(highlighted_lines.iter().map(|(line, highlights)| {
Label::new(
@@ -1637,7 +1637,7 @@ impl EditorElement {
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
enum JumpIcon {}
- MouseEventHandler::<JumpIcon, _>::new((*id).into(), cx, |state, _| {
+ MouseEventHandler::new::<JumpIcon, _>((*id).into(), cx, |state, _| {
let style = style.jump_icon.style_for(state);
Svg::new("icons/arrow_up_right_8.svg")
.with_color(style.color)
@@ -565,7 +565,7 @@ impl InfoPopover {
)
});
- MouseEventHandler::<InfoPopover, _>::new(0, cx, |_, cx| {
+ MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
let mut region_id = 0;
let view_id = cx.view_id();
@@ -654,7 +654,7 @@ impl DiagnosticPopover {
let tooltip_style = theme::current(cx).tooltip.clone();
- MouseEventHandler::<DiagnosticPopover, _>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<DiagnosticPopover, _>(0, cx, |_, _| {
text.with_soft_wrap(true)
.contained()
.with_style(container_style)
@@ -9,7 +9,7 @@ use crate::{
};
use anyhow::Context;
use clock::Global;
-use gpui::{ModelHandle, Task, ViewContext};
+use gpui::{ModelContext, ModelHandle, Task, ViewContext};
use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
use log::error;
use parking_lot::RwLock;
@@ -17,14 +17,21 @@ use project::InlayHint;
use collections::{hash_map, HashMap, HashSet};
use language::language_settings::InlayHintSettings;
+use sum_tree::Bias;
use util::post_inc;
pub struct InlayHintCache {
- pub hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
- pub allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
- pub version: usize,
- pub enabled: bool,
- update_tasks: HashMap<ExcerptId, UpdateTask>,
+ hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
+ allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
+ version: usize,
+ enabled: bool,
+ update_tasks: HashMap<ExcerptId, TasksForRanges>,
+}
+
+#[derive(Debug)]
+struct TasksForRanges {
+ tasks: Vec<Task<()>>,
+ sorted_ranges: Vec<Range<language::Anchor>>,
}
#[derive(Debug)]
@@ -32,7 +39,7 @@ pub struct CachedExcerptHints {
version: usize,
buffer_version: Global,
buffer_id: u64,
- pub hints: Vec<(InlayId, InlayHint)>,
+ hints: Vec<(InlayId, InlayHint)>,
}
#[derive(Debug, Clone, Copy)]
@@ -48,18 +55,6 @@ pub struct InlaySplice {
pub to_insert: Vec<Inlay>,
}
-struct UpdateTask {
- invalidate: InvalidationStrategy,
- cache_version: usize,
- task: RunningTask,
- pending_refresh: Option<ExcerptQuery>,
-}
-
-struct RunningTask {
- _task: Task<()>,
- is_running_rx: smol::channel::Receiver<()>,
-}
-
#[derive(Debug)]
struct ExcerptHintsUpdate {
excerpt_id: ExcerptId,
@@ -72,24 +67,10 @@ struct ExcerptHintsUpdate {
struct ExcerptQuery {
buffer_id: u64,
excerpt_id: ExcerptId,
- dimensions: ExcerptDimensions,
cache_version: usize,
invalidate: InvalidationStrategy,
}
-#[derive(Debug, Clone, Copy)]
-struct ExcerptDimensions {
- excerpt_range_start: language::Anchor,
- excerpt_range_end: language::Anchor,
- excerpt_visible_range_start: language::Anchor,
- excerpt_visible_range_end: language::Anchor,
-}
-
-struct HintFetchRanges {
- visible_range: Range<language::Anchor>,
- other_ranges: Vec<Range<language::Anchor>>,
-}
-
impl InvalidationStrategy {
fn should_invalidate(&self) -> bool {
matches!(
@@ -99,35 +80,92 @@ impl InvalidationStrategy {
}
}
-impl ExcerptQuery {
- fn hints_fetch_ranges(&self, buffer: &BufferSnapshot) -> HintFetchRanges {
- let visible_range =
- self.dimensions.excerpt_visible_range_start..self.dimensions.excerpt_visible_range_end;
- let mut other_ranges = Vec::new();
- if self
- .dimensions
- .excerpt_range_start
- .cmp(&visible_range.start, buffer)
- .is_lt()
- {
- let mut end = visible_range.start;
- end.offset -= 1;
- other_ranges.push(self.dimensions.excerpt_range_start..end);
- }
- if self
- .dimensions
- .excerpt_range_end
- .cmp(&visible_range.end, buffer)
- .is_gt()
- {
- let mut start = visible_range.end;
- start.offset += 1;
- other_ranges.push(start..self.dimensions.excerpt_range_end);
+impl TasksForRanges {
+ fn new(sorted_ranges: Vec<Range<language::Anchor>>, task: Task<()>) -> Self {
+ Self {
+ tasks: vec![task],
+ sorted_ranges,
}
+ }
- HintFetchRanges {
- visible_range,
- other_ranges: other_ranges.into_iter().map(|range| range).collect(),
+ fn update_cached_tasks(
+ &mut self,
+ buffer_snapshot: &BufferSnapshot,
+ query_range: Range<text::Anchor>,
+ invalidate: InvalidationStrategy,
+ spawn_task: impl FnOnce(Vec<Range<language::Anchor>>) -> Task<()>,
+ ) {
+ let ranges_to_query = match invalidate {
+ InvalidationStrategy::None => {
+ let mut ranges_to_query = Vec::new();
+ let mut latest_cached_range = None::<&mut Range<language::Anchor>>;
+ for cached_range in self
+ .sorted_ranges
+ .iter_mut()
+ .skip_while(|cached_range| {
+ cached_range
+ .end
+ .cmp(&query_range.start, buffer_snapshot)
+ .is_lt()
+ })
+ .take_while(|cached_range| {
+ cached_range
+ .start
+ .cmp(&query_range.end, buffer_snapshot)
+ .is_le()
+ })
+ {
+ match latest_cached_range {
+ Some(latest_cached_range) => {
+ if latest_cached_range.end.offset.saturating_add(1)
+ < cached_range.start.offset
+ {
+ ranges_to_query.push(latest_cached_range.end..cached_range.start);
+ cached_range.start = latest_cached_range.end;
+ }
+ }
+ None => {
+ if query_range
+ .start
+ .cmp(&cached_range.start, buffer_snapshot)
+ .is_lt()
+ {
+ ranges_to_query.push(query_range.start..cached_range.start);
+ cached_range.start = query_range.start;
+ }
+ }
+ }
+ latest_cached_range = Some(cached_range);
+ }
+
+ match latest_cached_range {
+ Some(latest_cached_range) => {
+ if latest_cached_range.end.offset.saturating_add(1) < query_range.end.offset
+ {
+ ranges_to_query.push(latest_cached_range.end..query_range.end);
+ latest_cached_range.end = query_range.end;
+ }
+ }
+ None => {
+ ranges_to_query.push(query_range.clone());
+ self.sorted_ranges.push(query_range);
+ self.sorted_ranges.sort_by(|range_a, range_b| {
+ range_a.start.cmp(&range_b.start, buffer_snapshot)
+ });
+ }
+ }
+
+ ranges_to_query
+ }
+ InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited => {
+ self.tasks.clear();
+ self.sorted_ranges.clear();
+ vec![query_range]
+ }
+ };
+
+ if !ranges_to_query.is_empty() {
+ self.tasks.push(spawn_task(ranges_to_query));
}
}
}
@@ -168,7 +206,6 @@ impl InlayHintCache {
);
if new_splice.is_some() {
self.version += 1;
- self.update_tasks.clear();
self.allowed_hint_kinds = new_allowed_hint_kinds;
}
ControlFlow::Break(new_splice)
@@ -197,7 +234,7 @@ impl InlayHintCache {
pub fn spawn_hint_refresh(
&mut self,
- mut excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
+ excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
invalidate: InvalidationStrategy,
cx: &mut ViewContext<Editor>,
) -> Option<InlaySplice> {
@@ -205,43 +242,23 @@ impl InlayHintCache {
return None;
}
- let update_tasks = &mut self.update_tasks;
let mut invalidated_hints = Vec::new();
if invalidate.should_invalidate() {
- let mut changed = false;
- update_tasks.retain(|task_excerpt_id, _| {
- let retain = excerpts_to_query.contains_key(task_excerpt_id);
- changed |= !retain;
- retain
- });
+ self.update_tasks
+ .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
self.hints.retain(|cached_excerpt, cached_hints| {
let retain = excerpts_to_query.contains_key(cached_excerpt);
- changed |= !retain;
if !retain {
invalidated_hints.extend(cached_hints.read().hints.iter().map(|&(id, _)| id));
}
retain
});
- if changed {
- self.version += 1;
- }
}
if excerpts_to_query.is_empty() && invalidated_hints.is_empty() {
return None;
}
- let cache_version = self.version;
- excerpts_to_query.retain(|visible_excerpt_id, _| {
- match update_tasks.entry(*visible_excerpt_id) {
- hash_map::Entry::Occupied(o) => match o.get().cache_version.cmp(&cache_version) {
- cmp::Ordering::Less => true,
- cmp::Ordering::Equal => invalidate.should_invalidate(),
- cmp::Ordering::Greater => false,
- },
- hash_map::Entry::Vacant(_) => true,
- }
- });
-
+ let cache_version = self.version + 1;
cx.spawn(|editor, mut cx| async move {
editor
.update(&mut cx, |editor, cx| {
@@ -368,6 +385,19 @@ impl InlayHintCache {
self.update_tasks.clear();
self.hints.clear();
}
+
+ pub fn hints(&self) -> Vec<InlayHint> {
+ let mut hints = Vec::new();
+ for excerpt_hints in self.hints.values() {
+ let excerpt_hints = excerpt_hints.read();
+ hints.extend(excerpt_hints.hints.iter().map(|(_, hint)| hint).cloned());
+ }
+ hints
+ }
+
+ pub fn version(&self) -> usize {
+ self.version
+ }
}
fn spawn_new_update_tasks(
@@ -378,13 +408,14 @@ fn spawn_new_update_tasks(
cx: &mut ViewContext<'_, '_, Editor>,
) {
let visible_hints = Arc::new(editor.visible_inlay_hints(cx));
- for (excerpt_id, (buffer_handle, new_task_buffer_version, excerpt_visible_range)) in
+ for (excerpt_id, (excerpt_buffer, new_task_buffer_version, excerpt_visible_range)) in
excerpts_to_query
{
if excerpt_visible_range.is_empty() {
continue;
}
- let buffer = buffer_handle.read(cx);
+ let buffer = excerpt_buffer.read(cx);
+ let buffer_id = buffer.remote_id();
let buffer_snapshot = buffer.snapshot();
if buffer_snapshot
.version()
@@ -402,202 +433,123 @@ fn spawn_new_update_tasks(
{
continue;
}
- if !new_task_buffer_version.changed_since(&cached_buffer_version)
- && !matches!(invalidate, InvalidationStrategy::RefreshRequested)
- {
- continue;
- }
};
- let buffer_id = buffer.remote_id();
- let excerpt_visible_range_start = buffer.anchor_before(excerpt_visible_range.start);
- let excerpt_visible_range_end = buffer.anchor_after(excerpt_visible_range.end);
-
- let (multi_buffer_snapshot, full_excerpt_range) =
+ let (multi_buffer_snapshot, Some(query_range)) =
editor.buffer.update(cx, |multi_buffer, cx| {
- let multi_buffer_snapshot = multi_buffer.snapshot(cx);
(
- multi_buffer_snapshot,
- multi_buffer
- .excerpts_for_buffer(&buffer_handle, cx)
- .into_iter()
- .find(|(id, _)| id == &excerpt_id)
- .map(|(_, range)| range.context),
+ multi_buffer.snapshot(cx),
+ determine_query_range(
+ multi_buffer,
+ excerpt_id,
+ &excerpt_buffer,
+ excerpt_visible_range,
+ cx,
+ ),
)
- });
+ }) else { return; };
+ let query = ExcerptQuery {
+ buffer_id,
+ excerpt_id,
+ cache_version: update_cache_version,
+ invalidate,
+ };
- if let Some(full_excerpt_range) = full_excerpt_range {
- let query = ExcerptQuery {
- buffer_id,
- excerpt_id,
- dimensions: ExcerptDimensions {
- excerpt_range_start: full_excerpt_range.start,
- excerpt_range_end: full_excerpt_range.end,
- excerpt_visible_range_start,
- excerpt_visible_range_end,
- },
- cache_version: update_cache_version,
- invalidate,
- };
+ let new_update_task = |fetch_ranges| {
+ new_update_task(
+ query,
+ fetch_ranges,
+ multi_buffer_snapshot,
+ buffer_snapshot.clone(),
+ Arc::clone(&visible_hints),
+ cached_excerpt_hints,
+ cx,
+ )
+ };
- let new_update_task = |is_refresh_after_regular_task| {
- new_update_task(
- query,
- multi_buffer_snapshot,
- buffer_snapshot,
- Arc::clone(&visible_hints),
- cached_excerpt_hints,
- is_refresh_after_regular_task,
- cx,
- )
- };
- match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
- hash_map::Entry::Occupied(mut o) => {
- let update_task = o.get_mut();
- match (update_task.invalidate, invalidate) {
- (_, InvalidationStrategy::None) => {}
- (
- InvalidationStrategy::BufferEdited,
- InvalidationStrategy::RefreshRequested,
- ) if !update_task.task.is_running_rx.is_closed() => {
- update_task.pending_refresh = Some(query);
- }
- _ => {
- o.insert(UpdateTask {
- invalidate,
- cache_version: query.cache_version,
- task: new_update_task(false),
- pending_refresh: None,
- });
- }
- }
- }
- hash_map::Entry::Vacant(v) => {
- v.insert(UpdateTask {
- invalidate,
- cache_version: query.cache_version,
- task: new_update_task(false),
- pending_refresh: None,
- });
- }
+ match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
+ hash_map::Entry::Occupied(mut o) => {
+ o.get_mut().update_cached_tasks(
+ &buffer_snapshot,
+ query_range,
+ invalidate,
+ new_update_task,
+ );
+ }
+ hash_map::Entry::Vacant(v) => {
+ v.insert(TasksForRanges::new(
+ vec![query_range.clone()],
+ new_update_task(vec![query_range]),
+ ));
}
}
}
}
+fn determine_query_range(
+ multi_buffer: &mut MultiBuffer,
+ excerpt_id: ExcerptId,
+ excerpt_buffer: &ModelHandle<Buffer>,
+ excerpt_visible_range: Range<usize>,
+ cx: &mut ModelContext<'_, MultiBuffer>,
+) -> Option<Range<language::Anchor>> {
+ let full_excerpt_range = multi_buffer
+ .excerpts_for_buffer(excerpt_buffer, cx)
+ .into_iter()
+ .find(|(id, _)| id == &excerpt_id)
+ .map(|(_, range)| range.context)?;
+
+ let buffer = excerpt_buffer.read(cx);
+ let excerpt_visible_len = excerpt_visible_range.end - excerpt_visible_range.start;
+ let start_offset = excerpt_visible_range
+ .start
+ .saturating_sub(excerpt_visible_len)
+ .max(full_excerpt_range.start.offset);
+ let start = buffer.anchor_before(buffer.clip_offset(start_offset, Bias::Left));
+ let end_offset = excerpt_visible_range
+ .end
+ .saturating_add(excerpt_visible_len)
+ .min(full_excerpt_range.end.offset)
+ .min(buffer.len());
+ let end = buffer.anchor_after(buffer.clip_offset(end_offset, Bias::Right));
+ if start.cmp(&end, buffer).is_eq() {
+ None
+ } else {
+ Some(start..end)
+ }
+}
+
fn new_update_task(
query: ExcerptQuery,
+ hint_fetch_ranges: Vec<Range<language::Anchor>>,
multi_buffer_snapshot: MultiBufferSnapshot,
buffer_snapshot: BufferSnapshot,
visible_hints: Arc<Vec<Inlay>>,
cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
- is_refresh_after_regular_task: bool,
cx: &mut ViewContext<'_, '_, Editor>,
-) -> RunningTask {
- let hints_fetch_ranges = query.hints_fetch_ranges(&buffer_snapshot);
- let (is_running_tx, is_running_rx) = smol::channel::bounded(1);
- let _task = cx.spawn(|editor, mut cx| async move {
- let _is_running_tx = is_running_tx;
- let create_update_task = |range| {
- fetch_and_update_hints(
- editor.clone(),
- multi_buffer_snapshot.clone(),
- buffer_snapshot.clone(),
- Arc::clone(&visible_hints),
- cached_excerpt_hints.as_ref().map(Arc::clone),
- query,
- range,
- cx.clone(),
- )
- };
-
- if is_refresh_after_regular_task {
- let visible_range_has_updates =
- match create_update_task(hints_fetch_ranges.visible_range).await {
- Ok(updated) => updated,
- Err(e) => {
- error!("inlay hint visible range update task failed: {e:#}");
- return;
- }
- };
-
- if visible_range_has_updates {
- let other_update_results = futures::future::join_all(
- hints_fetch_ranges
- .other_ranges
- .into_iter()
- .map(create_update_task),
+) -> Task<()> {
+ cx.spawn(|editor, cx| async move {
+ let task_update_results =
+ futures::future::join_all(hint_fetch_ranges.into_iter().map(|range| {
+ fetch_and_update_hints(
+ editor.clone(),
+ multi_buffer_snapshot.clone(),
+ buffer_snapshot.clone(),
+ Arc::clone(&visible_hints),
+ cached_excerpt_hints.as_ref().map(Arc::clone),
+ query,
+ range,
+ cx.clone(),
)
- .await;
-
- for result in other_update_results {
- if let Err(e) = result {
- error!("inlay hint update task failed: {e:#}");
- }
- }
- }
- } else {
- let task_update_results = futures::future::join_all(
- std::iter::once(hints_fetch_ranges.visible_range)
- .chain(hints_fetch_ranges.other_ranges.into_iter())
- .map(create_update_task),
- )
+ }))
.await;
- for result in task_update_results {
- if let Err(e) = result {
- error!("inlay hint update task failed: {e:#}");
- }
+ for result in task_update_results {
+ if let Err(e) = result {
+ error!("inlay hint update task failed: {e:#}");
}
}
-
- editor
- .update(&mut cx, |editor, cx| {
- let pending_refresh_query = editor
- .inlay_hint_cache
- .update_tasks
- .get_mut(&query.excerpt_id)
- .and_then(|task| task.pending_refresh.take());
-
- if let Some(pending_refresh_query) = pending_refresh_query {
- let refresh_multi_buffer = editor.buffer().read(cx);
- let refresh_multi_buffer_snapshot = refresh_multi_buffer.snapshot(cx);
- let refresh_visible_hints = Arc::new(editor.visible_inlay_hints(cx));
- let refresh_cached_excerpt_hints = editor
- .inlay_hint_cache
- .hints
- .get(&pending_refresh_query.excerpt_id)
- .map(Arc::clone);
- if let Some(buffer) =
- refresh_multi_buffer.buffer(pending_refresh_query.buffer_id)
- {
- editor.inlay_hint_cache.update_tasks.insert(
- pending_refresh_query.excerpt_id,
- UpdateTask {
- invalidate: InvalidationStrategy::RefreshRequested,
- cache_version: editor.inlay_hint_cache.version,
- task: new_update_task(
- pending_refresh_query,
- refresh_multi_buffer_snapshot,
- buffer.read(cx).snapshot(),
- refresh_visible_hints,
- refresh_cached_excerpt_hints,
- true,
- cx,
- ),
- pending_refresh: None,
- },
- );
- }
- }
- })
- .ok();
- });
-
- RunningTask {
- _task,
- is_running_rx,
- }
+ })
}
async fn fetch_and_update_hints(
@@ -609,7 +561,7 @@ async fn fetch_and_update_hints(
query: ExcerptQuery,
fetch_range: Range<language::Anchor>,
mut cx: gpui::AsyncAppContext,
-) -> anyhow::Result<bool> {
+) -> anyhow::Result<()> {
let inlay_hints_fetch_task = editor
.update(&mut cx, |editor, cx| {
editor
@@ -625,11 +577,10 @@ async fn fetch_and_update_hints(
})
.ok()
.flatten();
- let mut update_happened = false;
- let Some(inlay_hints_fetch_task) = inlay_hints_fetch_task else { return Ok(update_happened) };
- let new_hints = inlay_hints_fetch_task
- .await
- .context("inlay hint fetch task")?;
+ let new_hints = match inlay_hints_fetch_task {
+ Some(task) => task.await.context("inlay hint fetch task")?,
+ None => return Ok(()),
+ };
let background_task_buffer_snapshot = buffer_snapshot.clone();
let backround_fetch_range = fetch_range.clone();
let new_update = cx
@@ -645,106 +596,21 @@ async fn fetch_and_update_hints(
)
})
.await;
-
- editor
- .update(&mut cx, |editor, cx| {
- if let Some(new_update) = new_update {
- update_happened = !new_update.add_to_cache.is_empty()
- || !new_update.remove_from_cache.is_empty()
- || !new_update.remove_from_visible.is_empty();
-
- let cached_excerpt_hints = editor
- .inlay_hint_cache
- .hints
- .entry(new_update.excerpt_id)
- .or_insert_with(|| {
- Arc::new(RwLock::new(CachedExcerptHints {
- version: query.cache_version,
- buffer_version: buffer_snapshot.version().clone(),
- buffer_id: query.buffer_id,
- hints: Vec::new(),
- }))
- });
- let mut cached_excerpt_hints = cached_excerpt_hints.write();
- match query.cache_version.cmp(&cached_excerpt_hints.version) {
- cmp::Ordering::Less => return,
- cmp::Ordering::Greater | cmp::Ordering::Equal => {
- cached_excerpt_hints.version = query.cache_version;
- }
- }
- cached_excerpt_hints
- .hints
- .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id));
- cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
- editor.inlay_hint_cache.version += 1;
-
- let mut splice = InlaySplice {
- to_remove: new_update.remove_from_visible,
- to_insert: Vec::new(),
- };
-
- for new_hint in new_update.add_to_cache {
- let new_hint_position = multi_buffer_snapshot
- .anchor_in_excerpt(query.excerpt_id, new_hint.position);
- let new_inlay_id = post_inc(&mut editor.next_inlay_id);
- if editor
- .inlay_hint_cache
- .allowed_hint_kinds
- .contains(&new_hint.kind)
- {
- splice.to_insert.push(Inlay::hint(
- new_inlay_id,
- new_hint_position,
- &new_hint,
- ));
- }
-
- cached_excerpt_hints
- .hints
- .push((InlayId::Hint(new_inlay_id), new_hint));
- }
-
- cached_excerpt_hints
- .hints
- .sort_by(|(_, hint_a), (_, hint_b)| {
- hint_a.position.cmp(&hint_b.position, &buffer_snapshot)
- });
- drop(cached_excerpt_hints);
-
- if query.invalidate.should_invalidate() {
- let mut outdated_excerpt_caches = HashSet::default();
- for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
- let excerpt_hints = excerpt_hints.read();
- if excerpt_hints.buffer_id == query.buffer_id
- && excerpt_id != &query.excerpt_id
- && buffer_snapshot
- .version()
- .changed_since(&excerpt_hints.buffer_version)
- {
- outdated_excerpt_caches.insert(*excerpt_id);
- splice
- .to_remove
- .extend(excerpt_hints.hints.iter().map(|(id, _)| id));
- }
- }
- editor
- .inlay_hint_cache
- .hints
- .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
- }
-
- let InlaySplice {
- to_remove,
- to_insert,
- } = splice;
- if !to_remove.is_empty() || !to_insert.is_empty() {
- editor.splice_inlay_hints(to_remove, to_insert, cx)
- }
- }
- })
- .ok();
-
- Ok(update_happened)
+ if let Some(new_update) = new_update {
+ editor
+ .update(&mut cx, |editor, cx| {
+ apply_hint_update(
+ editor,
+ new_update,
+ query,
+ buffer_snapshot,
+ multi_buffer_snapshot,
+ cx,
+ );
+ })
+ .ok();
+ }
+ Ok(())
}
fn calculate_hint_updates(
@@ -793,19 +659,6 @@ fn calculate_hint_updates(
visible_hints
.iter()
.filter(|hint| hint.position.excerpt_id == query.excerpt_id)
- .filter(|hint| {
- contains_position(&fetch_range, hint.position.text_anchor, buffer_snapshot)
- })
- .filter(|hint| {
- fetch_range
- .start
- .cmp(&hint.position.text_anchor, buffer_snapshot)
- .is_le()
- && fetch_range
- .end
- .cmp(&hint.position.text_anchor, buffer_snapshot)
- .is_ge()
- })
.map(|inlay_hint| inlay_hint.id)
.filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
);
@@ -819,16 +672,6 @@ fn calculate_hint_updates(
.filter(|(cached_inlay_id, _)| {
!excerpt_hints_to_persist.contains_key(cached_inlay_id)
})
- .filter(|(_, cached_hint)| {
- fetch_range
- .start
- .cmp(&cached_hint.position, buffer_snapshot)
- .is_le()
- && fetch_range
- .end
- .cmp(&cached_hint.position, buffer_snapshot)
- .is_ge()
- })
.map(|(cached_inlay_id, _)| *cached_inlay_id),
);
}
@@ -855,6 +698,113 @@ fn contains_position(
&& range.end.cmp(&position, buffer_snapshot).is_ge()
}
+fn apply_hint_update(
+ editor: &mut Editor,
+ new_update: ExcerptHintsUpdate,
+ query: ExcerptQuery,
+ buffer_snapshot: BufferSnapshot,
+ multi_buffer_snapshot: MultiBufferSnapshot,
+ cx: &mut ViewContext<'_, '_, Editor>,
+) {
+ let cached_excerpt_hints = editor
+ .inlay_hint_cache
+ .hints
+ .entry(new_update.excerpt_id)
+ .or_insert_with(|| {
+ Arc::new(RwLock::new(CachedExcerptHints {
+ version: query.cache_version,
+ buffer_version: buffer_snapshot.version().clone(),
+ buffer_id: query.buffer_id,
+ hints: Vec::new(),
+ }))
+ });
+ let mut cached_excerpt_hints = cached_excerpt_hints.write();
+ match query.cache_version.cmp(&cached_excerpt_hints.version) {
+ cmp::Ordering::Less => return,
+ cmp::Ordering::Greater | cmp::Ordering::Equal => {
+ cached_excerpt_hints.version = query.cache_version;
+ }
+ }
+
+ let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
+ cached_excerpt_hints
+ .hints
+ .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id));
+ let mut splice = InlaySplice {
+ to_remove: new_update.remove_from_visible,
+ to_insert: Vec::new(),
+ };
+ for new_hint in new_update.add_to_cache {
+ let cached_hints = &mut cached_excerpt_hints.hints;
+ let insert_position = match cached_hints
+ .binary_search_by(|probe| probe.1.position.cmp(&new_hint.position, &buffer_snapshot))
+ {
+ Ok(i) => {
+ if cached_hints[i].1.text() == new_hint.text() {
+ None
+ } else {
+ Some(i)
+ }
+ }
+ Err(i) => Some(i),
+ };
+
+ if let Some(insert_position) = insert_position {
+ let new_inlay_id = post_inc(&mut editor.next_inlay_id);
+ if editor
+ .inlay_hint_cache
+ .allowed_hint_kinds
+ .contains(&new_hint.kind)
+ {
+ let new_hint_position =
+ multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
+ splice
+ .to_insert
+ .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
+ }
+ cached_hints.insert(insert_position, (InlayId::Hint(new_inlay_id), new_hint));
+ cached_inlays_changed = true;
+ }
+ }
+ cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
+ drop(cached_excerpt_hints);
+
+ if query.invalidate.should_invalidate() {
+ let mut outdated_excerpt_caches = HashSet::default();
+ for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
+ let excerpt_hints = excerpt_hints.read();
+ if excerpt_hints.buffer_id == query.buffer_id
+ && excerpt_id != &query.excerpt_id
+ && buffer_snapshot
+ .version()
+ .changed_since(&excerpt_hints.buffer_version)
+ {
+ outdated_excerpt_caches.insert(*excerpt_id);
+ splice
+ .to_remove
+ .extend(excerpt_hints.hints.iter().map(|(id, _)| id));
+ }
+ }
+ cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
+ editor
+ .inlay_hint_cache
+ .hints
+ .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
+ }
+
+ let InlaySplice {
+ to_remove,
+ to_insert,
+ } = splice;
+ let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
+ if cached_inlays_changed || displayed_inlays_changed {
+ editor.inlay_hint_cache.version += 1;
+ }
+ if displayed_inlays_changed {
+ editor.splice_inlay_hints(to_remove, to_insert, cx)
+ }
+}
+
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
@@ -866,6 +816,7 @@ mod tests {
};
use futures::StreamExt;
use gpui::{executor::Deterministic, TestAppContext, ViewHandle};
+ use itertools::Itertools;
use language::{
language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
};
@@ -873,7 +824,7 @@ mod tests {
use parking_lot::Mutex;
use project::{FakeFs, Project};
use settings::SettingsStore;
- use text::Point;
+ use text::{Point, ToPoint};
use workspace::Workspace;
use crate::editor_tests::update_test_language_settings;
@@ -1879,7 +1830,7 @@ mod tests {
task_lsp_request_ranges.lock().push(params.range);
let query_start = params.range.start;
- let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
+ let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
Ok(Some(vec![lsp::InlayHint {
position: query_start,
label: lsp::InlayHintLabel::String(i.to_string()),
@@ -1894,18 +1845,44 @@ mod tests {
})
.next()
.await;
+ fn editor_visible_range(
+ editor: &ViewHandle<Editor>,
+ cx: &mut gpui::TestAppContext,
+ ) -> Range<Point> {
+ let ranges = editor.update(cx, |editor, cx| editor.excerpt_visible_offsets(None, cx));
+ assert_eq!(
+ ranges.len(),
+ 1,
+ "Single buffer should produce a single excerpt with visible range"
+ );
+ let (_, (excerpt_buffer, _, excerpt_visible_range)) =
+ ranges.into_iter().next().unwrap();
+ excerpt_buffer.update(cx, |buffer, _| {
+ let snapshot = buffer.snapshot();
+ let start = buffer
+ .anchor_before(excerpt_visible_range.start)
+ .to_point(&snapshot);
+ let end = buffer
+ .anchor_after(excerpt_visible_range.end)
+ .to_point(&snapshot);
+ start..end
+ })
+ }
+
+ let initial_visible_range = editor_visible_range(&editor, cx);
+ let expected_initial_query_range_end =
+ lsp::Position::new(initial_visible_range.end.row * 2, 1);
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
- let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
- ranges.sort_by_key(|range| range.start);
- assert_eq!(ranges.len(), 2, "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints");
- assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
- assert_eq!(ranges[0].end.line, ranges[1].start.line, "Both requests should be on the same line");
- assert_eq!(ranges[0].end.character + 1, ranges[1].start.character, "Both request should be concequent");
-
- assert_eq!(lsp_request_count.load(Ordering::SeqCst), 2,
- "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints");
- let expected_layers = vec!["1".to_string(), "2".to_string()];
+ let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
+ assert_eq!(ranges.len(), 1,
+ "When scroll is at the edge of a big document, double of its visible part range should be queried for hints in one single big request, but got: {ranges:?}");
+ let query_range = &ranges[0];
+ assert_eq!(query_range.start, lsp::Position::new(0, 0), "Should query initially from the beginning of the document");
+ assert_eq!(query_range.end, expected_initial_query_range_end, "Should query initially for double lines of the visible part of the document");
+
+ assert_eq!(lsp_request_count.load(Ordering::Acquire), 1);
+ let expected_layers = vec!["1".to_string()];
assert_eq!(
expected_layers,
cached_hint_labels(editor),
@@ -1913,37 +1890,114 @@ mod tests {
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(
- editor.inlay_hint_cache().version, 2,
- "Both LSP queries should've bumped the cache version"
+ editor.inlay_hint_cache().version, 1,
+ "LSP queries should've bumped the cache version"
);
});
editor.update(cx, |editor, cx| {
editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([600..600]));
- editor.handle_input("++++more text++++", cx);
});
+ let visible_range_after_scrolls = editor_visible_range(&editor, cx);
+ let visible_line_count =
+ editor.update(cx, |editor, _| editor.visible_line_count().unwrap());
cx.foreground().run_until_parked();
+ let selection_in_cached_range = editor.update(cx, |editor, cx| {
+ let ranges = lsp_request_ranges
+ .lock()
+ .drain(..)
+ .sorted_by_key(|r| r.start)
+ .collect::<Vec<_>>();
+ assert_eq!(
+ ranges.len(),
+ 2,
+ "Should query 2 ranges after both scrolls, but got: {ranges:?}"
+ );
+ let first_scroll = &ranges[0];
+ let second_scroll = &ranges[1];
+ assert_eq!(
+ first_scroll.end, second_scroll.start,
+ "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
+ );
+ assert_eq!(
+ first_scroll.start, expected_initial_query_range_end,
+ "First scroll should start the query right after the end of the original scroll",
+ );
+ assert_eq!(
+ second_scroll.end,
+ lsp::Position::new(
+ visible_range_after_scrolls.end.row
+ + visible_line_count.ceil() as u32,
+ 0
+ ),
+ "Second scroll should query one more screen down after the end of the visible range"
+ );
+
+ assert_eq!(
+ lsp_request_count.load(Ordering::Acquire),
+ 3,
+ "Should query for hints after every scroll"
+ );
+ let expected_layers = vec!["1".to_string(), "2".to_string(), "3".to_string()];
+ assert_eq!(
+ expected_layers,
+ cached_hint_labels(editor),
+ "Should have hints from the new LSP response after the edit"
+ );
+ assert_eq!(expected_layers, visible_hint_labels(editor, cx));
+ assert_eq!(
+ editor.inlay_hint_cache().version,
+ 3,
+ "Should update the cache for every LSP response with hints added"
+ );
+
+ let mut selection_in_cached_range = visible_range_after_scrolls.end;
+ selection_in_cached_range.row -= visible_line_count.ceil() as u32;
+ selection_in_cached_range
+ });
+
+ editor.update(cx, |editor, cx| {
+ editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+ s.select_ranges([selection_in_cached_range..selection_in_cached_range])
+ });
+ });
+ cx.foreground().run_until_parked();
+ editor.update(cx, |_, _| {
+ let ranges = lsp_request_ranges
+ .lock()
+ .drain(..)
+ .sorted_by_key(|r| r.start)
+ .collect::<Vec<_>>();
+ assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
+ assert_eq!(lsp_request_count.load(Ordering::Acquire), 3);
+ });
+
editor.update(cx, |editor, cx| {
- let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
- ranges.sort_by_key(|range| range.start);
- assert_eq!(ranges.len(), 3, "When scroll is at the middle of a big document, its visible part + 2 other inbisible parts should be queried for hints");
- assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
- assert_eq!(ranges[0].end.line + 1, ranges[1].start.line, "Neighbour requests got on different lines due to the line end");
- assert_ne!(ranges[0].end.character, 0, "First query was in the end of the line, not in the beginning");
- assert_eq!(ranges[1].start.character, 0, "Second query got pushed into a new line and starts from the beginning");
- assert_eq!(ranges[1].end.line, ranges[2].start.line, "Neighbour requests should be on the same line");
- assert_eq!(ranges[1].end.character + 1, ranges[2].start.character, "Neighbour request should be concequent");
-
- assert_eq!(lsp_request_count.load(Ordering::SeqCst), 5,
- "When scroll not at the edge of a big document, visible part + 2 other parts should be queried for hints");
- let expected_layers = vec!["3".to_string(), "4".to_string(), "5".to_string()];
+ editor.handle_input("++++more text++++", cx);
+ });
+ cx.foreground().run_until_parked();
+ editor.update(cx, |editor, cx| {
+ let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
+ assert_eq!(ranges.len(), 1,
+ "On edit, should scroll to selection and query a range around it. Instead, got query ranges {ranges:?}");
+ let query_range = &ranges[0];
+ assert!(query_range.start.line < selection_in_cached_range.row,
+ "Hints should be queried with the selected range after the query range start");
+ assert!(query_range.end.line > selection_in_cached_range.row,
+ "Hints should be queried with the selected range before the query range end");
+ assert!(query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
+ "Hints query range should contain one more screen before");
+ assert!(query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
+ "Hints query range should contain one more screen after");
+
+ assert_eq!(lsp_request_count.load(Ordering::Acquire), 4, "Should query for hints once after the edit");
+ let expected_layers = vec!["4".to_string()];
assert_eq!(expected_layers, cached_hint_labels(editor),
- "Should have hints from the new LSP response after edit");
+ "Should have hints from the new LSP response after the edit");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, 5, "Should update the cache for every LSP response with hints added");
+ assert_eq!(editor.inlay_hint_cache().version, 4, "Should update the cache for every LSP response with hints added");
});
}
@@ -13,7 +13,7 @@ use gpui::{
};
use language::{Bias, Point};
use util::ResultExt;
-use workspace::{item::Item, WorkspaceId};
+use workspace::WorkspaceId;
use crate::{
display_map::{DisplaySnapshot, ToDisplayPoint},
@@ -333,9 +333,7 @@ impl Editor {
cx,
);
- if !self.is_singleton(cx) {
- self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx);
- }
+ self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx);
}
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
@@ -35,7 +35,7 @@ impl View for DeployFeedbackButton {
let theme = theme::current(cx).clone();
Stack::new()
.with_child(
- MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<Self, _>(0, cx, |state, _| {
let style = &theme
.workspace
.status_bar
@@ -41,7 +41,7 @@ impl View for FeedbackInfoText {
.aligned(),
)
.with_child(
- MouseEventHandler::<OpenZedCommunityRepo, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<OpenZedCommunityRepo, _>(0, cx, |state, _| {
let contained_text = if state.hovered() {
&theme.feedback.link_text_hover
} else {
@@ -52,7 +52,7 @@ impl View for SubmitFeedbackButton {
.map_or(true, |i| i.read(cx).allow_submission);
enum SubmitFeedbackButton {}
- MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<SubmitFeedbackButton, _>(0, cx, |state, _| {
let text;
let style = if allow_submission {
text = "Submit as Markdown";
@@ -0,0 +1,335 @@
+use button_component::Button;
+
+use component::AdaptComponent;
+use gpui::{
+ color::Color,
+ elements::{ContainerStyle, Flex, Label, ParentElement},
+ fonts::{self, TextStyle},
+ platform::WindowOptions,
+ AnyElement, App, Element, Entity, View, ViewContext,
+};
+use log::LevelFilter;
+use pathfinder_geometry::vector::vec2f;
+use simplelog::SimpleLogger;
+use theme::Toggleable;
+use toggleable_button::ToggleableButton;
+
+fn main() {
+ SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+ App::new(()).unwrap().run(|cx| {
+ cx.platform().activate(true);
+ cx.add_window(WindowOptions::with_bounds(vec2f(300., 200.)), |_| {
+ TestView {
+ count: 0,
+ is_doubling: false,
+ }
+ });
+ });
+}
+
+pub struct TestView {
+ count: usize,
+ is_doubling: bool,
+}
+
+impl TestView {
+ fn increase_count(&mut self) {
+ if self.is_doubling {
+ self.count *= 2;
+ } else {
+ self.count += 1;
+ }
+ }
+}
+
+impl Entity for TestView {
+ type Event = ();
+}
+
+type ButtonStyle = ContainerStyle;
+
+impl View for TestView {
+ fn ui_name() -> &'static str {
+ "TestView"
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
+ fonts::with_font_cache(cx.font_cache.to_owned(), || {
+ Flex::column()
+ .with_child(Label::new(
+ format!("Count: {}", self.count),
+ TextStyle::for_color(Color::red()),
+ ))
+ .with_child(
+ Button::new(move |_, v: &mut Self, cx| {
+ v.increase_count();
+ cx.notify();
+ })
+ .with_text(
+ "Hello from a counting BUTTON",
+ TextStyle::for_color(Color::blue()),
+ )
+ .with_style(ButtonStyle::fill(Color::yellow()))
+ .into_element(),
+ )
+ .with_child(
+ ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| {
+ v.is_doubling = !v.is_doubling;
+ cx.notify();
+ })
+ .with_text("Double the count?", TextStyle::for_color(Color::black()))
+ .with_style(Toggleable {
+ inactive: ButtonStyle::fill(Color::red()),
+ active: ButtonStyle::fill(Color::green()),
+ })
+ .into_element(),
+ )
+ .expanded()
+ .contained()
+ .with_background_color(Color::white())
+ .into_any()
+ })
+ }
+}
+
+mod theme {
+ pub struct Toggleable<T> {
+ pub inactive: T,
+ pub active: T,
+ }
+
+ impl<T> Toggleable<T> {
+ pub fn style_for(&self, active: bool) -> &T {
+ if active {
+ &self.active
+ } else {
+ &self.inactive
+ }
+ }
+ }
+}
+
+// Component creation:
+mod toggleable_button {
+ use gpui::{
+ elements::{ContainerStyle, LabelStyle},
+ scene::MouseClick,
+ EventContext, View,
+ };
+
+ use crate::{button_component::Button, component::Component, theme::Toggleable};
+
+ pub struct ToggleableButton<V: View> {
+ active: bool,
+ style: Option<Toggleable<ContainerStyle>>,
+ button: Button<V>,
+ }
+
+ impl<V: View> ToggleableButton<V> {
+ pub fn new<F>(active: bool, on_click: F) -> Self
+ where
+ F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
+ {
+ Self {
+ active,
+ button: Button::new(on_click),
+ style: None,
+ }
+ }
+
+ pub fn with_text(self, text: &str, style: impl Into<LabelStyle>) -> ToggleableButton<V> {
+ ToggleableButton {
+ active: self.active,
+ style: self.style,
+ button: self.button.with_text(text, style),
+ }
+ }
+
+ pub fn with_style(self, style: Toggleable<ContainerStyle>) -> ToggleableButton<V> {
+ ToggleableButton {
+ active: self.active,
+ style: Some(style),
+ button: self.button,
+ }
+ }
+ }
+
+ impl<V: View> Component for ToggleableButton<V> {
+ type View = V;
+
+ fn render(
+ self,
+ v: &mut Self::View,
+ cx: &mut gpui::ViewContext<Self::View>,
+ ) -> gpui::AnyElement<V> {
+ let button = if let Some(style) = self.style {
+ self.button.with_style(*style.style_for(self.active))
+ } else {
+ self.button
+ };
+ button.render(v, cx)
+ }
+ }
+}
+
+mod button_component {
+
+ use gpui::{
+ elements::{ContainerStyle, Label, LabelStyle, MouseEventHandler},
+ platform::MouseButton,
+ scene::MouseClick,
+ AnyElement, Element, EventContext, TypeTag, View, ViewContext,
+ };
+
+ use crate::component::Component;
+
+ type ClickHandler<V> = Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>)>;
+
+ pub struct Button<V: View> {
+ click_handler: ClickHandler<V>,
+ tag: TypeTag,
+ contents: Option<AnyElement<V>>,
+ style: Option<ContainerStyle>,
+ }
+
+ impl<V: View> Button<V> {
+ pub fn new<F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static>(handler: F) -> Self {
+ Self {
+ click_handler: Box::new(handler),
+ tag: TypeTag::new::<F>(),
+ style: None,
+ contents: None,
+ }
+ }
+
+ pub fn with_text(mut self, text: &str, style: impl Into<LabelStyle>) -> Self {
+ self.contents = Some(Label::new(text.to_string(), style).into_any());
+ self
+ }
+
+ pub fn _with_contents<E: Element<V>>(mut self, contents: E) -> Self {
+ self.contents = Some(contents.into_any());
+ self
+ }
+
+ pub fn with_style(mut self, style: ContainerStyle) -> Self {
+ self.style = Some(style);
+ self
+ }
+ }
+
+ impl<V: View> Component for Button<V> {
+ type View = V;
+
+ fn render(self, _: &mut Self::View, cx: &mut ViewContext<V>) -> AnyElement<Self::View> {
+ let click_handler = self.click_handler;
+
+ let result = MouseEventHandler::new_dynamic(self.tag, 0, cx, |_, _| {
+ self.contents
+ .unwrap_or_else(|| gpui::elements::Empty::new().into_any())
+ })
+ .on_click(MouseButton::Left, move |click, v, cx| {
+ click_handler(click, v, cx);
+ })
+ .contained();
+
+ let result = if let Some(style) = self.style {
+ result.with_style(style)
+ } else {
+ result
+ };
+
+ result.into_any()
+ }
+ }
+}
+
+mod component {
+
+ use gpui::{AnyElement, Element, View, ViewContext};
+ use pathfinder_geometry::vector::Vector2F;
+
+ // Public API:
+ pub trait Component {
+ type View: View;
+
+ fn render(
+ self,
+ v: &mut Self::View,
+ cx: &mut ViewContext<Self::View>,
+ ) -> AnyElement<Self::View>;
+ }
+
+ pub struct ComponentAdapter<E> {
+ component: Option<E>,
+ }
+
+ impl<E> ComponentAdapter<E> {
+ pub fn new(e: E) -> Self {
+ Self { component: Some(e) }
+ }
+ }
+
+ pub trait AdaptComponent<C: Component>: Sized {
+ fn into_element(self) -> ComponentAdapter<Self> {
+ ComponentAdapter::new(self)
+ }
+ }
+
+ impl<C: Component> AdaptComponent<C> for C {}
+
+ impl<C: Component + 'static> Element<C::View> for ComponentAdapter<C> {
+ type LayoutState = AnyElement<C::View>;
+
+ type PaintState = ();
+
+ fn layout(
+ &mut self,
+ constraint: gpui::SizeConstraint,
+ view: &mut C::View,
+ cx: &mut gpui::LayoutContext<C::View>,
+ ) -> (Vector2F, Self::LayoutState) {
+ let component = self.component.take().unwrap();
+ let mut element = component.render(view, cx.view_context());
+ let constraint = element.layout(constraint, view, cx);
+ (constraint, element)
+ }
+
+ fn paint(
+ &mut self,
+ scene: &mut gpui::SceneBuilder,
+ bounds: gpui::geometry::rect::RectF,
+ visible_bounds: gpui::geometry::rect::RectF,
+ layout: &mut Self::LayoutState,
+ view: &mut C::View,
+ cx: &mut gpui::PaintContext<C::View>,
+ ) -> Self::PaintState {
+ layout.paint(scene, bounds.origin(), visible_bounds, view, cx)
+ }
+
+ fn rect_for_text_range(
+ &self,
+ _: std::ops::Range<usize>,
+ _: gpui::geometry::rect::RectF,
+ _: gpui::geometry::rect::RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &C::View,
+ _: &ViewContext<C::View>,
+ ) -> Option<gpui::geometry::rect::RectF> {
+ todo!()
+ }
+
+ fn debug(
+ &self,
+ _: gpui::geometry::rect::RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &C::View,
+ _: &ViewContext<C::View>,
+ ) -> serde_json::Value {
+ todo!()
+ }
+ }
+}
@@ -3280,7 +3280,11 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
}
pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
- let region_id = MouseRegionId::new::<Tag>(self.view_id, region_id);
+ self.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id)
+ }
+
+ pub fn mouse_state_dynamic(&self, tag: TypeTag, region_id: usize) -> MouseState {
+ let region_id = MouseRegionId::new(tag, self.view_id, region_id);
MouseState {
hovered: self.window.hovered_region_ids.contains(®ion_id),
clicked: if let Some((clicked_region_id, button)) = self.window.clicked_region {
@@ -3321,6 +3325,36 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
}
}
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct TypeTag {
+ tag: TypeId,
+ #[cfg(debug_assertions)]
+ tag_type_name: &'static str,
+}
+
+impl TypeTag {
+ pub fn new<Tag: 'static>() -> Self {
+ Self {
+ tag: TypeId::of::<Tag>(),
+ #[cfg(debug_assertions)]
+ tag_type_name: std::any::type_name::<Tag>(),
+ }
+ }
+
+ pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
+ Self {
+ tag,
+ #[cfg(debug_assertions)]
+ tag_type_name: type_name,
+ }
+ }
+
+ #[cfg(debug_assertions)]
+ pub(crate) fn type_name(&self) -> &'static str {
+ self.tag_type_name
+ }
+}
+
impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
BorrowAppContext::read_with(&*self.window_context, f)
@@ -5171,7 +5205,7 @@ mod tests {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
enum Handler {}
let mouse_down_count = self.mouse_down_count.clone();
- MouseEventHandler::<Handler, _>::new(0, cx, |_, _| Empty::new())
+ MouseEventHandler::new::<Handler, _>(0, cx, |_, _| Empty::new())
.on_down(MouseButton::Left, move |_, _, _| {
mouse_down_count.fetch_add(1, SeqCst);
})
@@ -197,11 +197,11 @@ pub trait Element<V: View>: 'static {
Resizable::new(self.into_any(), side, size, on_resize)
}
- fn mouse<Tag>(self, region_id: usize) -> MouseEventHandler<Tag, V>
+ fn mouse<Tag: 'static>(self, region_id: usize) -> MouseEventHandler<V>
where
Self: Sized,
{
- MouseEventHandler::for_child(self.into_any(), region_id)
+ MouseEventHandler::for_child::<Tag>(self.into_any(), region_id)
}
}
@@ -38,6 +38,15 @@ pub struct ContainerStyle {
pub cursor: Option<CursorStyle>,
}
+impl ContainerStyle {
+ pub fn fill(color: Color) -> Self {
+ Self {
+ background_color: Some(color),
+ ..Default::default()
+ }
+ }
+}
+
pub struct Container<V: View> {
child: AnyElement<V>,
style: ContainerStyle,
@@ -11,12 +11,12 @@ use crate::{
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
},
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
- SceneBuilder, SizeConstraint, View, ViewContext,
+ SceneBuilder, SizeConstraint, TypeTag, View, ViewContext,
};
use serde_json::json;
-use std::{marker::PhantomData, ops::Range};
+use std::ops::Range;
-pub struct MouseEventHandler<Tag: 'static, V: View> {
+pub struct MouseEventHandler<V: View> {
child: AnyElement<V>,
region_id: usize,
cursor_style: Option<CursorStyle>,
@@ -26,13 +26,13 @@ pub struct MouseEventHandler<Tag: 'static, V: View> {
notify_on_click: bool,
above: bool,
padding: Padding,
- _tag: PhantomData<Tag>,
+ tag: TypeTag,
}
/// Element which provides a render_child callback with a MouseState and paints a mouse
/// region under (or above) it for easy mouse event handling.
-impl<Tag, V: View> MouseEventHandler<Tag, V> {
- pub fn for_child(child: impl Element<V>, region_id: usize) -> Self {
+impl<V: View> MouseEventHandler<V> {
+ pub fn for_child<Tag: 'static>(child: impl Element<V>, region_id: usize) -> Self {
Self {
child: child.into_any(),
region_id,
@@ -43,16 +43,19 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
hoverable: false,
above: false,
padding: Default::default(),
- _tag: PhantomData,
+ tag: TypeTag::new::<Tag>(),
}
}
- pub fn new<E, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
+ pub fn new<Tag: 'static, E>(
+ region_id: usize,
+ cx: &mut ViewContext<V>,
+ render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
+ ) -> Self
where
E: Element<V>,
- F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
{
- let mut mouse_state = cx.mouse_state::<Tag>(region_id);
+ let mut mouse_state = cx.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id);
let child = render_child(&mut mouse_state, cx).into_any();
let notify_on_hover = mouse_state.accessed_hovered();
let notify_on_click = mouse_state.accessed_clicked();
@@ -66,19 +69,46 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
hoverable: true,
above: false,
padding: Default::default(),
- _tag: PhantomData,
+ tag: TypeTag::new::<Tag>(),
+ }
+ }
+
+ pub fn new_dynamic(
+ tag: TypeTag,
+ region_id: usize,
+ cx: &mut ViewContext<V>,
+ render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> AnyElement<V>,
+ ) -> Self {
+ let mut mouse_state = cx.mouse_state_dynamic(tag, region_id);
+ let child = render_child(&mut mouse_state, cx);
+ let notify_on_hover = mouse_state.accessed_hovered();
+ let notify_on_click = mouse_state.accessed_clicked();
+ Self {
+ child,
+ region_id,
+ cursor_style: None,
+ handlers: Default::default(),
+ notify_on_hover,
+ notify_on_click,
+ hoverable: true,
+ above: false,
+ padding: Default::default(),
+ tag,
}
}
/// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
/// for drag and drop handling and similar events which should be captured before the child
/// gets the opportunity
- pub fn above<D, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
+ pub fn above<Tag: 'static, D>(
+ region_id: usize,
+ cx: &mut ViewContext<V>,
+ render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
+ ) -> Self
where
D: Element<V>,
- F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
{
- let mut handler = Self::new(region_id, cx, render_child);
+ let mut handler = Self::new::<Tag, _>(region_id, cx, render_child);
handler.above = true;
handler
}
@@ -223,7 +253,8 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
});
}
scene.push_mouse_region(
- MouseRegion::from_handlers::<Tag>(
+ MouseRegion::from_handlers(
+ self.tag,
cx.view_id(),
self.region_id,
hit_bounds,
@@ -236,7 +267,7 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
}
}
-impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
+impl<V: View> Element<V> for MouseEventHandler<V> {
type LayoutState = ();
type PaintState = ();
@@ -95,7 +95,7 @@ impl<V: View> Tooltip<V> {
} else {
None
};
- let child = MouseEventHandler::<MouseEventHandlerState<Tag>, _>::new(id, cx, |_, _| child)
+ let child = MouseEventHandler::new::<MouseEventHandlerState<Tag>, _>(id, cx, |_, _| child)
.on_hover(move |e, _, cx| {
let position = e.position;
if e.started {
@@ -72,6 +72,13 @@ pub struct TextStyle {
}
impl TextStyle {
+ pub fn for_color(color: Color) -> Self {
+ Self {
+ color,
+ ..Default::default()
+ }
+ }
+
pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
TextStyle {
color: refinement.color.unwrap_or(self.color),
@@ -24,6 +24,7 @@ use crate::{
use anyhow::{anyhow, bail, Result};
use async_task::Runnable;
pub use event::*;
+use pathfinder_geometry::vector::vec2f;
use postage::oneshot;
use schemars::JsonSchema;
use serde::Deserialize;
@@ -180,6 +181,16 @@ pub struct WindowOptions<'a> {
pub screen: Option<Rc<dyn Screen>>,
}
+impl<'a> WindowOptions<'a> {
+ pub fn with_bounds(bounds: Vector2F) -> Self {
+ Self {
+ bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), bounds)),
+ center: true,
+ ..Default::default()
+ }
+ }
+}
+
#[derive(Debug)]
pub struct TitlebarOptions<'a> {
pub title: Option<&'a str>,
@@ -1,13 +1,10 @@
-use crate::{platform::MouseButton, window::WindowContext, EventContext, View, ViewContext};
+use crate::{
+ platform::MouseButton, window::WindowContext, EventContext, TypeTag, View, ViewContext,
+};
use collections::HashMap;
use pathfinder_geometry::rect::RectF;
use smallvec::SmallVec;
-use std::{
- any::{Any, TypeId},
- fmt::Debug,
- mem::Discriminant,
- rc::Rc,
-};
+use std::{any::Any, fmt::Debug, mem::Discriminant, rc::Rc};
use super::{
mouse_event::{
@@ -33,14 +30,27 @@ impl MouseRegion {
/// should pass a different (consistent) region_id. If you have one big region that covers your
/// whole component, just pass the view_id again.
pub fn new<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
- Self::from_handlers::<Tag>(view_id, region_id, bounds, Default::default())
+ Self::from_handlers(
+ TypeTag::new::<Tag>(),
+ view_id,
+ region_id,
+ bounds,
+ Default::default(),
+ )
}
pub fn handle_all<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
- Self::from_handlers::<Tag>(view_id, region_id, bounds, HandlerSet::capture_all())
+ Self::from_handlers(
+ TypeTag::new::<Tag>(),
+ view_id,
+ region_id,
+ bounds,
+ HandlerSet::capture_all(),
+ )
}
- pub fn from_handlers<Tag: 'static>(
+ pub fn from_handlers(
+ tag: TypeTag,
view_id: usize,
region_id: usize,
bounds: RectF,
@@ -49,10 +59,8 @@ impl MouseRegion {
Self {
id: MouseRegionId {
view_id,
- tag: TypeId::of::<Tag>(),
+ tag,
region_id,
- #[cfg(debug_assertions)]
- tag_type_name: std::any::type_name::<Tag>(),
},
bounds,
handlers,
@@ -180,20 +188,16 @@ impl MouseRegion {
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
pub struct MouseRegionId {
view_id: usize,
- tag: TypeId,
+ tag: TypeTag,
region_id: usize,
- #[cfg(debug_assertions)]
- tag_type_name: &'static str,
}
impl MouseRegionId {
- pub(crate) fn new<Tag: 'static>(view_id: usize, region_id: usize) -> Self {
+ pub(crate) fn new(tag: TypeTag, view_id: usize, region_id: usize) -> Self {
MouseRegionId {
view_id,
region_id,
- tag: TypeId::of::<Tag>(),
- #[cfg(debug_assertions)]
- tag_type_name: std::any::type_name::<Tag>(),
+ tag,
}
}
@@ -203,7 +207,7 @@ impl MouseRegionId {
#[cfg(debug_assertions)]
pub fn tag_type_name(&self) -> &'static str {
- self.tag_type_name
+ self.tag.type_name()
}
}
@@ -106,7 +106,7 @@ impl View for Select {
Default::default()
};
let mut result = Flex::column().with_child(
- MouseEventHandler::<Header, _>::new(self.handle.id(), cx, |mouse_state, cx| {
+ MouseEventHandler::new::<Header, _>(self.handle.id(), cx, |mouse_state, cx| {
(self.render_item)(
self.selected_item_ix,
ItemType::Header,
@@ -130,7 +130,7 @@ impl View for Select {
let selected_item_ix = this.selected_item_ix;
range.end = range.end.min(this.item_count);
items.extend(range.map(|ix| {
- MouseEventHandler::<Item, _>::new(ix, cx, |mouse_state, cx| {
+ MouseEventHandler::new::<Item, _>(ix, cx, |mouse_state, cx| {
(this.render_item)(
ix,
if ix == selected_item_ix {
@@ -53,7 +53,7 @@ impl View for ActiveBufferLanguage {
"Unknown".to_string()
};
- MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
+ MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
let theme = &theme::current(cx).workspace.status_bar;
let style = theme.active_language.style_for(state);
Label::new(active_language_text, style.text.clone())
@@ -573,7 +573,7 @@ impl View for LspLogToolbarItemView {
.with_children(if self.menu_open {
Some(
Overlay::new(
- MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
+ MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
Flex::column()
.with_children(menu_rows.into_iter().map(|row| {
Self::render_language_server_menu_item(
@@ -672,7 +672,7 @@ impl LspLogToolbarItemView {
cx: &mut ViewContext<Self>,
) -> impl Element<Self> {
enum ToggleMenu {}
- MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
+ MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, cx| {
let label: Cow<str> = current_server
.and_then(|row| {
let worktree = row.worktree.read(cx);
@@ -728,7 +728,7 @@ impl LspLogToolbarItemView {
.with_height(theme.toolbar_dropdown_menu.row_height)
})
.with_child(
- MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, _| {
+ MouseEventHandler::new::<ActivateLog, _>(id.0, cx, move |state, _| {
let style = theme
.toolbar_dropdown_menu
.item
@@ -746,7 +746,7 @@ impl LspLogToolbarItemView {
}),
)
.with_child(
- MouseEventHandler::<ActivateRpcTrace, _>::new(id.0, cx, move |state, cx| {
+ MouseEventHandler::new::<ActivateRpcTrace, _>(id.0, cx, move |state, cx| {
let style = theme
.toolbar_dropdown_menu
.item
@@ -389,7 +389,7 @@ impl View for SyntaxTreeView {
{
let layer = layer.clone();
let theme = editor_theme.clone();
- return MouseEventHandler::<Self, Self>::new(0, cx, move |state, cx| {
+ return MouseEventHandler::new::<Self, _>(0, cx, move |state, cx| {
let list_hovered = state.hovered();
UniformList::new(
self.list_state.clone(),
@@ -505,7 +505,7 @@ impl SyntaxTreeToolbarItemView {
.with_child(Self::render_header(&theme, &active_layer, cx))
.with_children(self.menu_open.then(|| {
Overlay::new(
- MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
+ MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
Flex::column()
.with_children(active_buffer.syntax_layers().enumerate().map(
|(ix, layer)| {
@@ -564,7 +564,7 @@ impl SyntaxTreeToolbarItemView {
cx: &mut ViewContext<Self>,
) -> impl Element<Self> {
enum ToggleMenu {}
- MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, _| {
+ MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, _| {
let style = theme.toolbar_dropdown_menu.header.style_for(state);
Flex::row()
.with_child(
@@ -596,7 +596,7 @@ impl SyntaxTreeToolbarItemView {
cx: &mut ViewContext<Self>,
) -> impl Element<Self> {
enum ActivateLayer {}
- MouseEventHandler::<ActivateLayer, _>::new(layer_ix, cx, move |state, _| {
+ MouseEventHandler::new::<ActivateLayer, _>(layer_ix, cx, move |state, _| {
let is_selected = layer.node() == active_layer.node();
let style = theme
.toolbar_dropdown_menu
@@ -434,7 +434,9 @@ impl LanguageServer {
..Default::default()
}),
inlay_hint: Some(InlayHintClientCapabilities {
- resolve_support: None,
+ resolve_support: Some(InlayHintResolveClientCapabilities {
+ properties: vec!["textEdits".to_string(), "tooltip".to_string()],
+ }),
dynamic_registration: Some(false),
}),
..Default::default()
@@ -113,7 +113,7 @@ impl<D: PickerDelegate> View for Picker<D> {
let selected_ix = this.delegate.selected_index();
range.end = cmp::min(range.end, this.delegate.match_count());
items.extend(range.map(move |ix| {
- MouseEventHandler::<D, _>::new(ix, cx, |state, cx| {
+ MouseEventHandler::new::<D, _>(ix, cx, |state, cx| {
this.delegate.render_match(ix, state, ix == selected_ix, cx)
})
// Capture mouse events
@@ -1954,7 +1954,7 @@ impl LspCommand for InlayHints {
_: &mut Project,
_: PeerId,
buffer_version: &clock::Global,
- cx: &mut AppContext,
+ _: &mut AppContext,
) -> proto::InlayHintsResponse {
proto::InlayHintsResponse {
hints: response
@@ -1963,51 +1963,17 @@ impl LspCommand for InlayHints {
position: Some(language::proto::serialize_anchor(&response_hint.position)),
padding_left: response_hint.padding_left,
padding_right: response_hint.padding_right,
- label: Some(proto::InlayHintLabel {
- label: Some(match response_hint.label {
- InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s),
- InlayHintLabel::LabelParts(label_parts) => {
- proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts {
- parts: label_parts.into_iter().map(|label_part| proto::InlayHintLabelPart {
- value: label_part.value,
- tooltip: label_part.tooltip.map(|tooltip| {
- let proto_tooltip = match tooltip {
- InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s),
- InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent {
- kind: markup_content.kind,
- value: markup_content.value,
- }),
- };
- proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)}
- }),
- location: label_part.location.map(|location| proto::Location {
- start: Some(serialize_anchor(&location.range.start)),
- end: Some(serialize_anchor(&location.range.end)),
- buffer_id: location.buffer.read(cx).remote_id(),
- }),
- }).collect()
- })
- }
- }),
- }),
kind: response_hint.kind.map(|kind| kind.name().to_string()),
- tooltip: response_hint.tooltip.map(|response_tooltip| {
- let proto_tooltip = match response_tooltip {
- InlayHintTooltip::String(s) => {
- proto::inlay_hint_tooltip::Content::Value(s)
- }
- InlayHintTooltip::MarkupContent(markup_content) => {
- proto::inlay_hint_tooltip::Content::MarkupContent(
- proto::MarkupContent {
- kind: markup_content.kind,
- value: markup_content.value,
- },
- )
- }
- };
- proto::InlayHintTooltip {
- content: Some(proto_tooltip),
- }
+ // Do not pass extra data such as tooltips to clients: host can put tooltip data from the cache during resolution.
+ tooltip: None,
+ // Similarly, do not pass label parts to clients: host can return a detailed list during resolution.
+ label: Some(proto::InlayHintLabel {
+ label: Some(proto::inlay_hint_label::Label::Value(
+ match response_hint.label {
+ InlayHintLabel::String(s) => s,
+ InlayHintLabel::LabelParts(_) => response_hint.text(),
+ },
+ )),
}),
})
.collect(),
@@ -1407,7 +1407,7 @@ impl ProjectPanel {
let show_editor = details.is_editing && !details.is_processing;
- MouseEventHandler::<Self, _>::new(entry_id.to_usize(), cx, |state, cx| {
+ MouseEventHandler::new::<Self, _>(entry_id.to_usize(), cx, |state, cx| {
let mut style = entry_style
.in_state(details.is_selected)
.style_for(state)
@@ -1519,7 +1519,7 @@ impl View for ProjectPanel {
if has_worktree {
Stack::new()
.with_child(
- MouseEventHandler::<ProjectPanel, _>::new(0, cx, |_, cx| {
+ MouseEventHandler::new::<ProjectPanel, _>(0, cx, |_, cx| {
UniformList::new(
self.list.clone(),
self.visible_entries
@@ -1563,7 +1563,7 @@ impl View for ProjectPanel {
} else {
Flex::column()
.with_child(
- MouseEventHandler::<Self, _>::new(2, cx, {
+ MouseEventHandler::new::<Self, _>(2, cx, {
let button_style = theme.open_project_button.clone();
let context_menu_item_style = theme::current(cx).context_menu.item.clone();
move |state, cx| {
@@ -416,7 +416,7 @@ impl BufferSearchBar {
let tooltip_style = theme::current(cx).tooltip.clone();
let is_active = self.search_options.contains(option);
Some(
- MouseEventHandler::<Self, _>::new(option.bits as usize, cx, |state, cx| {
+ MouseEventHandler::new::<Self, _>(option.bits as usize, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme
.search
@@ -463,7 +463,7 @@ impl BufferSearchBar {
let tooltip_style = theme::current(cx).tooltip.clone();
enum NavButton {}
- MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
+ MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme.search.option_button.inactive_state().style_for(state);
Label::new(icon, style.text.clone())
@@ -497,7 +497,7 @@ impl BufferSearchBar {
let action_type_id = 0_usize;
enum ActionButton {}
- MouseEventHandler::<ActionButton, _>::new(action_type_id, cx, |state, cx| {
+ MouseEventHandler::new::<ActionButton, _>(action_type_id, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme.search.action_button.style_for(state);
Label::new(icon, style.text.clone())
@@ -527,7 +527,7 @@ impl BufferSearchBar {
let tooltip_style = theme::current(cx).tooltip.clone();
enum CloseButton {}
- MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<CloseButton, _>(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
@@ -328,7 +328,7 @@ impl View for ProjectSearchView {
editor.set_placeholder_text(new_placeholder_text, cx);
});
- MouseEventHandler::<Status, _>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<Status, _>(0, cx, |_, _| {
Label::new(text, theme.search.results_status.clone())
.aligned()
.contained()
@@ -1103,7 +1103,7 @@ impl ProjectSearchBar {
let tooltip_style = theme::current(cx).tooltip.clone();
enum NavButton {}
- MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
+ MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme.search.option_button.inactive_state().style_for(state);
Label::new(icon, style.text.clone())
@@ -1134,7 +1134,7 @@ impl ProjectSearchBar {
) -> AnyElement<Self> {
let tooltip_style = theme::current(cx).tooltip.clone();
let is_active = self.is_option_enabled(option, cx);
- MouseEventHandler::<Self, _>::new(option.bits as usize, cx, |state, cx| {
+ MouseEventHandler::new::<Self, _>(option.bits as usize, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme
.search
@@ -1170,7 +1170,7 @@ impl ProjectSearchBar {
let region_id = 3;
- MouseEventHandler::<Self, _>::new(region_id, cx, |state, cx| {
+ MouseEventHandler::new::<Self, _>(region_id, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme
.search
@@ -34,7 +34,7 @@ pub fn checkbox<Tag, V, F>(
id: usize,
cx: &mut ViewContext<V>,
change: F,
-) -> MouseEventHandler<Tag, V>
+) -> MouseEventHandler<V>
where
Tag: 'static,
V: View,
@@ -43,7 +43,7 @@ where
let label = Label::new(label, style.label.text.clone())
.contained()
.with_style(style.label.container);
- checkbox_with_label(label, style, checked, id, cx, change)
+ checkbox_with_label::<Tag, _, _, _>(label, style, checked, id, cx, change)
}
pub fn checkbox_with_label<Tag, D, V, F>(
@@ -53,14 +53,14 @@ pub fn checkbox_with_label<Tag, D, V, F>(
id: usize,
cx: &mut ViewContext<V>,
change: F,
-) -> MouseEventHandler<Tag, V>
+) -> MouseEventHandler<V>
where
Tag: 'static,
D: Element<V>,
V: View,
F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
{
- MouseEventHandler::new(id, cx, |state, _| {
+ MouseEventHandler::new::<Tag, _>(id, cx, |state, _| {
let indicator = if checked {
svg(&style.icon)
} else {
@@ -153,14 +153,14 @@ pub fn cta_button<Tag, L, V, F>(
style: &ButtonStyle,
cx: &mut ViewContext<V>,
f: F,
-) -> MouseEventHandler<Tag, V>
+) -> MouseEventHandler<V>
where
Tag: 'static,
L: Into<Cow<'static, str>>,
V: View,
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
{
- MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<Tag, _>(0, cx, |state, _| {
let style = style.style_for(state);
Label::new(label, style.text.to_owned())
.aligned()
@@ -215,7 +215,7 @@ where
))
.with_child(
// FIXME: Get a better tag type
- MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
+ MouseEventHandler::new::<Tag, _>(999999, cx, |state, _cx| {
let style = style.close_icon.style_for(state);
icon(style)
})
@@ -295,7 +295,7 @@ impl PickerDelegate for BranchListDelegate {
let style = theme.picker.footer.clone();
enum BranchCreateButton {}
Some(
- Flex::row().with_child(MouseEventHandler::<BranchCreateButton, _>::new(0, cx, |state, _| {
+ Flex::row().with_child(MouseEventHandler::new::<BranchCreateButton, _>(0, cx, |state, _| {
let style = style.style_for(state);
Label::new("Create branch", style.label.clone())
.contained()
@@ -509,7 +509,7 @@ impl View for PanelButtons {
Some(
Stack::new()
.with_child(
- MouseEventHandler::<Self, _>::new(panel_ix, cx, |state, cx| {
+ MouseEventHandler::new::<Self, _>(panel_ix, cx, |state, cx| {
let style = button_style.in_state(is_active);
let style = style.style_for(state);
@@ -290,7 +290,7 @@ pub mod simple_message_notification {
.flex(1., true),
)
.with_child(
- MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
@@ -319,7 +319,7 @@ pub mod simple_message_notification {
.with_children({
click_message
.map(|click_message| {
- MouseEventHandler::<MessageNotificationTag, _>::new(
+ MouseEventHandler::new::<MessageNotificationTag, _>(
0,
cx,
|state, _| {
@@ -1211,7 +1211,7 @@ impl Pane {
enum Tab {}
let mouse_event_handler =
- MouseEventHandler::<Tab, Pane>::new(ix, cx, |_, cx| {
+ MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
Self::render_tab(
&item,
pane.clone(),
@@ -1420,7 +1420,7 @@ impl Pane {
let item_id = item.id();
enum TabCloseButton {}
let icon = Svg::new("icons/x_mark_8.svg");
- MouseEventHandler::<TabCloseButton, _>::new(item_id, cx, |mouse_state, _| {
+ MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
if mouse_state.hovered() {
icon.with_color(tab_style.icon_close_active)
} else {
@@ -1485,7 +1485,7 @@ impl Pane {
) -> AnyElement<Pane> {
enum TabBarButton {}
- let mut button = MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
+ let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
Svg::new(icon)
@@ -1547,7 +1547,7 @@ impl View for Pane {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
enum MouseNavigationHandler {}
- MouseEventHandler::<MouseNavigationHandler, _>::new(0, cx, |_, cx| {
+ MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
let active_item_index = self.active_item_index;
if let Some(active_item) = self.active_item() {
@@ -1559,7 +1559,7 @@ impl View for Pane {
enum TabBarEventHandler {}
stack.add_child(
- MouseEventHandler::<TabBarEventHandler, _>::new(0, cx, |_, _| {
+ MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
Empty::new()
.contained()
.with_style(theme.workspace.tab_bar.container)
@@ -19,7 +19,7 @@ pub fn dragged_item_receiver<Tag, D, F>(
split_margin: Option<f32>,
cx: &mut ViewContext<Pane>,
render_child: F,
-) -> MouseEventHandler<Tag, Pane>
+) -> MouseEventHandler<Pane>
where
Tag: 'static,
D: Element<Pane>,
@@ -39,7 +39,7 @@ where
None
};
- let mut handler = MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
+ let mut handler = MouseEventHandler::above::<Tag, _>(region_id, cx, |state, cx| {
// Observing hovered will cause a render when the mouse enters regardless
// of if mouse position was accessed before
let drag_position = if state.hovered() { drag_position } else { None };
@@ -212,7 +212,7 @@ impl Member {
let leader_user_id = leader.user.id;
let app_state = Arc::downgrade(app_state);
Some(
- MouseEventHandler::<FollowIntoExternalProject, _>::new(
+ MouseEventHandler::new::<FollowIntoExternalProject, _>(
pane.id(),
cx,
|_, _| {
@@ -72,7 +72,7 @@ impl View for SharedScreen {
enum Focus {}
let frame = self.frame.clone();
- MouseEventHandler::<Focus, _>::new(0, cx, |_, cx| {
+ MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
Canvas::new(move |scene, bounds, _, _, _| {
if let Some(frame) = frame.clone() {
let size = constrain_size_preserving_aspect_ratio(
@@ -223,7 +223,7 @@ fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>
action_name: &'static str,
cx: &mut ViewContext<Toolbar>,
) -> AnyElement<Toolbar> {
- MouseEventHandler::<A, _>::new(0, cx, |state, _| {
+ MouseEventHandler::new::<A, _>(0, cx, |state, _| {
let style = if enabled {
style.style_for(state)
} else {
@@ -2564,7 +2564,7 @@ impl Workspace {
};
enum TitleBar {}
- MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
+ MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
Stack::new()
.with_children(
self.titlebar_item
@@ -2653,7 +2653,7 @@ impl Workspace {
if self.project.read(cx).is_read_only() {
enum DisconnectedOverlay {}
Some(
- MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
+ MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
let theme = &theme::current(cx);
Label::new(
"Your connection to the remote project has been lost.",
@@ -3763,7 +3763,7 @@ impl View for Workspace {
// Prevent clicks within the modal from falling
// through to the rest of the workspace.
enum ModalBackground {}
- MouseEventHandler::<ModalBackground, _>::new(
+ MouseEventHandler::new::<ModalBackground, _>(
0,
cx,
|_, cx| ChildView::new(modal.view.as_any(), cx),