Share application adapter implementations (reduce LLVM IR bloat again) (#64262)

Move settings-update routing, fallible deserialization recovery,
selection effects, context-menu initialization, picker resize handles,
and ACP client setup into shared implementations. Inline the small
button callback adapters.

`cargo llvm-lines --locked -p settings_ui`:
Before (bec63dee23): 1571017 lines, 53634 copies
After: 1177376 lines (-25.1%), 43380 copies (-19.1%)

With compiler wrappers disabled, `cargo clean --target-dir
target/binary-size-split-build` and `cargo build --locked -p zed`:
Before: 135.6s wall, 1569.8s CPU, zed binary 1175563800 bytes 
After: 140.9s wall (+3.9%), 1588.8s CPU (+1.2%), zed binary 1157304600
bytes (-1.6%, -18.3MB)

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov authored and GitHub committed 2026-09-16 16:03:09 +00:00
1 parent 5e04c64b52
commit 8b9a2df705
9 files changed
+245 -165

No files matched your search

+25 -17
View File
@@ -7,7 +7,9 @@ use agent_client_protocol::schema::{
ProtocolVersion, ProtocolVersion,
v1::{self as acp, ErrorCode}, v1::{self as acp, ErrorCode},
}; };
use agent_client_protocol::{Agent, Client, ConnectionTo, JsonRpcResponse, Lines, Responder}; use agent_client_protocol::{
Agent, Builder, Client, ConnectionTo, HandleDispatchFrom, JsonRpcResponse, Lines, Responder,
};
use anyhow::anyhow; use anyhow::anyhow;
use async_channel; use async_channel;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
@@ -667,23 +669,39 @@ fn connect_client_future(
dispatch_tx: mpsc::UnboundedSender<ForegroundWork>, dispatch_tx: mpsc::UnboundedSender<ForegroundWork>,
connection_tx: futures::channel::oneshot::Sender<ConnectionTo<Agent>>, connection_tx: futures::channel::oneshot::Sender<ConnectionTo<Agent>>,
) -> impl Future<Output = Result<(), acp::Error>> { ) -> impl Future<Output = Result<(), acp::Error>> {
client_builder(name, dispatch_tx).connect_with(
transport,
move |connection: ConnectionTo<Agent>| async move {
if connection_tx.send(connection).is_err() {
log::error!("failed to send ACP connection handle — receiver was dropped");
}
// Keep the connection alive until the transport closes.
futures::future::pending::<Result<(), acp::Error>>().await
},
)
}
fn client_builder(
name: &'static str,
dispatch_sender: mpsc::UnboundedSender<ForegroundWork>,
) -> Builder<Client, impl HandleDispatchFrom<Agent>> {
// Each handler forwards its inputs onto the foreground dispatch queue. // Each handler forwards its inputs onto the foreground dispatch queue.
// The SDK requires the closure to be `Send`, so we move a clone of // The SDK requires the closure to be `Send`, so we move a clone of
// `dispatch_tx` into each one. // `dispatch_tx` into each one.
macro_rules! on_request { macro_rules! on_request {
($handler:ident) => {{ ($handler:ident) => {{
let dispatch_tx = dispatch_tx.clone(); let dispatch_sender = dispatch_sender.clone();
async move |req, responder, _connection| { async move |request, responder, _connection| {
enqueue_request(&dispatch_tx, req, responder, $handler); enqueue_request(&dispatch_sender, request, responder, $handler);
Ok(()) Ok(())
} }
}}; }};
} }
macro_rules! on_notification { macro_rules! on_notification {
($handler:ident) => {{ ($handler:ident) => {{
let dispatch_tx = dispatch_tx.clone(); let dispatch_sender = dispatch_sender.clone();
async move |notif, _connection| { async move |notification, _connection| {
enqueue_notification(&dispatch_tx, notif, $handler); enqueue_notification(&dispatch_sender, notification, $handler);
Ok(()) Ok(())
} }
}}; }};
@@ -738,16 +756,6 @@ fn connect_client_future(
on_notification!(handle_complete_elicitation), on_notification!(handle_complete_elicitation),
agent_client_protocol::on_receive_notification!(), agent_client_protocol::on_receive_notification!(),
) )
.connect_with(
transport,
move |connection: ConnectionTo<Agent>| async move {
if connection_tx.send(connection).is_err() {
log::error!("failed to send ACP connection handle — receiver was dropped");
}
// Keep the connection alive until the transport closes.
futures::future::pending::<Result<(), acp::Error>>().await
},
)
} }
fn client_capabilities_for_agent( fn client_capabilities_for_agent(
+42 -22
View File
@@ -66,24 +66,10 @@ impl Editor {
state.changed |= changed; state.changed |= changed;
return result; return result;
} }
let mut state = DeferredSelectionEffectsState { let mut state = self.prepare_selection_effects(effects);
changed: false,
effects,
old_cursor_position: self.selections.newest_anchor().head(),
history_entry: SelectionHistoryEntry {
selections: self.selections.disjoint_anchors_arc(),
select_next_state: self.select_next_state.clone(),
select_prev_state: self.select_prev_state.clone(),
add_selections_state: self.add_selections_state.clone(),
},
};
let (changed, result) = self.selections.change_with(&snapshot, change); let (changed, result) = self.selections.change_with(&snapshot, change);
state.changed = state.changed || changed; state.changed = state.changed || changed;
if self.defer_selection_effects { self.apply_selection_effects(state, window, cx);
self.deferred_selection_effects_state = Some(state);
} else {
self.apply_selection_effects(state, window, cx);
}
result result
} }
@@ -119,12 +105,7 @@ impl Editor {
let already_deferred = self.defer_selection_effects; let already_deferred = self.defer_selection_effects;
self.defer_selection_effects = true; self.defer_selection_effects = true;
let result = update(self, window, cx); let result = update(self, window, cx);
if !already_deferred { self.finish_deferred_selection_effects(already_deferred, window, cx);
self.defer_selection_effects = false;
if let Some(state) = self.deferred_selection_effects_state.take() {
self.apply_selection_effects(state, window, cx);
}
}
result result
} }
@@ -1776,12 +1757,51 @@ impl Editor {
cx.notify(); cx.notify();
} }
#[inline(never)]
fn prepare_selection_effects(
&self,
effects: SelectionEffects,
) -> DeferredSelectionEffectsState {
DeferredSelectionEffectsState {
changed: false,
effects,
old_cursor_position: self.selections.newest_anchor().head(),
history_entry: SelectionHistoryEntry {
selections: self.selections.disjoint_anchors_arc(),
select_next_state: self.select_next_state.clone(),
select_prev_state: self.select_prev_state.clone(),
add_selections_state: self.add_selections_state.clone(),
},
}
}
#[inline(never)]
fn finish_deferred_selection_effects(
&mut self,
already_deferred: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
if !already_deferred {
self.defer_selection_effects = false;
if let Some(state) = self.deferred_selection_effects_state.take() {
self.apply_selection_effects(state, window, cx);
}
}
}
#[inline(never)]
fn apply_selection_effects( fn apply_selection_effects(
&mut self, &mut self,
state: DeferredSelectionEffectsState, state: DeferredSelectionEffectsState,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
if self.defer_selection_effects {
self.deferred_selection_effects_state = Some(state);
return;
}
if state.changed { if state.changed {
self.selection_history.push(state.history_entry); self.selection_history.push(state.history_entry);
+55 -44
View File
@@ -58,7 +58,9 @@
use std::{any::type_name, marker::PhantomData}; use std::{any::type_name, marker::PhantomData};
use gpui::{ClickEvent, Context, CursorStyle, DragMoveEvent, MouseButton, Point, Styled, Window}; use gpui::{
ClickEvent, Context, CursorStyle, DragMoveEvent, MouseButton, Point, Stateful, Styled, Window,
};
use ui::prelude::*; use ui::prelude::*;
use crate::shape::{Centered, PositionAndShape, Shape, SizeBounds}; use crate::shape::{Centered, PositionAndShape, Shape, SizeBounds};
@@ -430,50 +432,31 @@ impl<D: PickerDelegate> Picker<D> {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
div() render_resize_handle(
.id(S::id()) side,
.absolute() self.shape,
.cursor(side.cursor()) &self.size_bounds,
.map(|this| { self.preview_layout_rendered(window),
side.position( window,
this, )
self.shape.clamped_position_and_size( .on_drag_move::<ResizeDrag<S>>(cx.listener(
self.preview_layout_rendered(window), move |picker, event: &DragMoveEvent<ResizeDrag<S>>, window, cx| {
&self.size_bounds, let drag = event.drag(cx);
window, let delta = event.event.position - drag.mouse_pos_before;
), let mut working = side.current_position_and_shape(drag.shape_before, delta);
side.clamp(
&mut working,
&picker.size_bounds,
picker.preview_layout_rendered(window),
window, window,
) );
}) picker.shape = Shape::Resizing(working);
.block_mouse_except_scroll() cx.notify();
.on_mouse_down(MouseButton::Left, do_nothing) },
.on_drag( ))
ResizeDrag::<S>::start_new( .on_click(cx.listener(move |picker, event: &ClickEvent, window, cx| {
self.shape, picker.reset_size_to_default_on_double_click(side, event, window, cx)
&self.size_bounds, }))
self.preview_layout_rendered(window),
window,
),
|_, _, _, cx| cx.new(|_| DragPreview),
)
.on_drag_move::<ResizeDrag<S>>(cx.listener(
move |this, event: &DragMoveEvent<ResizeDrag<S>>, window, cx| {
let drag = event.drag(cx);
let delta = event.event.position - drag.mouse_pos_before;
let mut working = side.current_position_and_shape(drag.shape_before, delta);
side.clamp(
&mut working,
&this.size_bounds,
this.preview_layout_rendered(window),
window,
);
this.shape = Shape::Resizing(working);
cx.notify();
},
))
.on_click(cx.listener(move |this, event: &ClickEvent, window, cx| {
this.reset_size_to_default_on_double_click(side, event, window, cx)
}))
} }
fn reset_size_to_default_on_double_click<S: Side>( fn reset_size_to_default_on_double_click<S: Side>(
@@ -497,6 +480,34 @@ impl<D: PickerDelegate> Picker<D> {
} }
} }
#[inline(never)]
#[track_caller]
fn render_resize_handle<S: Side>(
side: S,
shape: Shape,
bounds: &SizeBounds,
layout: Option<Layout>,
window: &mut Window,
) -> Stateful<Div> {
div()
.id(S::id())
.absolute()
.cursor(side.cursor())
.map(|handle| {
side.position(
handle,
shape.clamped_position_and_size(layout, bounds, window),
window,
)
})
.block_mouse_except_scroll()
.on_mouse_down(MouseButton::Left, do_nothing)
.on_drag(
ResizeDrag::<S>::start_new(shape, bounds, layout, window),
|_, _, _, cx| cx.new(|_| DragPreview),
)
}
fn do_nothing(_: &gpui::MouseDownEvent, window: &mut Window, cx: &mut App) { fn do_nothing(_: &gpui::MouseDownEvent, window: &mut Window, cx: &mut App) {
window.prevent_default(); window.prevent_default();
cx.stop_propagation(); cx.stop_propagation();
@@ -1,4 +1,4 @@
use std::cell::RefCell; use std::{cell::RefCell, fmt::Display};
use serde::Deserialize; use serde::Deserialize;
@@ -50,14 +50,22 @@ where
{ {
match T::deserialize(deserializer) { match T::deserialize(deserializer) {
Ok(value) => Ok(value), Ok(value) => Ok(value),
Err(e) => ERRORS.with_borrow_mut(|errors| { Err(error) => {
if let Some(errors) = errors { let mut error = Some(error);
errors.push(anyhow::anyhow!("{}", e)); let mut fallback = None;
Ok(Default::default()) recover_deserialization_error(&mut |record_error| {
} else { if let Some(error) = error.take() {
Err(e) record_error(&error);
let value = T::default();
drop(error);
fallback = Some(value);
}
});
match error {
Some(error) => Err(error),
None => Ok(fallback.expect("recovery creates a fallback")),
} }
}), }
} }
} }
@@ -146,6 +154,15 @@ where
T::deserialize(rest) T::deserialize(rest)
} }
#[inline(never)]
fn recover_deserialization_error(recover: &mut dyn FnMut(&mut dyn FnMut(&dyn Display))) {
ERRORS.with_borrow_mut(|errors| {
if let Some(errors) = errors {
recover(&mut |error| errors.push(anyhow::anyhow!("{error}")));
}
});
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serde::Deserialize; use serde::Deserialize;
+18 -3
View File
@@ -4694,6 +4694,20 @@ fn update_settings_file(
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
update: impl 'static + Send + FnOnce(&mut SettingsContent, &App), update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
) -> Result<()> {
let mut update = Some(update);
update_settings_file_inner(file, file_name, window, cx, &mut || {
Box::new(update.take().expect("called once"))
})
}
#[inline(never)]
fn update_settings_file_inner(
file: SettingsUiFile,
file_name: Option<&'static str>,
window: &mut Window,
cx: &mut App,
update: &mut dyn FnMut() -> Box<dyn Send + FnOnce(&mut SettingsContent, &App)>,
) -> Result<()> { ) -> Result<()> {
telemetry::event!("Settings Change", setting = file_name, type = file.setting_type()); telemetry::event!("Settings Change", setting = file_name, type = file.setting_type());
@@ -4708,7 +4722,7 @@ fn update_settings_file(
} }
SettingsUiFile::User => { SettingsUiFile::User => {
// todo(settings_ui) error? // todo(settings_ui) error?
SettingsStore::global(cx).update_settings_file(<dyn fs::Fs>::global(cx), update); SettingsStore::global(cx).update_settings_file(<dyn fs::Fs>::global(cx), update());
Ok(()) Ok(())
} }
SettingsUiFile::Server(_) => unimplemented!(), SettingsUiFile::Server(_) => unimplemented!(),
@@ -4833,10 +4847,11 @@ impl ProjectSettingsUpdateQueue {
} }
} }
#[inline(never)]
fn update_project_setting_file( fn update_project_setting_file(
worktree_id: WorktreeId, worktree_id: WorktreeId,
rel_path: Arc<RelPath>, rel_path: Arc<RelPath>,
update: impl 'static + FnOnce(&mut SettingsContent, &App), update: &mut dyn FnMut() -> Box<dyn Send + FnOnce(&mut SettingsContent, &App)>,
settings_window: Entity<SettingsWindow>, settings_window: Entity<SettingsWindow>,
cx: &mut App, cx: &mut App,
) -> Result<()> { ) -> Result<()> {
@@ -4857,7 +4872,7 @@ fn update_project_setting_file(
settings_window: settings_window.downgrade(), settings_window: settings_window.downgrade(),
project: project.downgrade(), project: project.downgrade(),
worktree: worktree.downgrade(), worktree: worktree.downgrade(),
update: Box::new(update), update: update(),
}; };
ProjectSettingsUpdateQueue::enqueue(cx, entry); ProjectSettingsUpdateQueue::enqueue(cx, entry);
@@ -314,6 +314,7 @@ impl Disableable for Button {
} }
impl Clickable for Button { impl Clickable for Button {
#[inline(always)]
fn on_click( fn on_click(
mut self, mut self,
handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static, handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
@@ -393,6 +394,7 @@ impl ButtonCommon for Button {
/// Button::new("tooltip_button", "Hover Me") /// Button::new("tooltip_button", "Hover Me")
/// .tooltip(Tooltip::text("This is a tooltip")); /// .tooltip(Tooltip::text("This is a tooltip"));
/// ``` /// ```
#[inline(always)]
fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self { fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
self.base = self.base.tooltip(tooltip); self.base = self.base.tooltip(tooltip);
self self
@@ -670,6 +670,7 @@ impl SelectableButton for ButtonLike {
} }
impl Clickable for ButtonLike { impl Clickable for ButtonLike {
#[inline(always)]
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self { fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Box::new(handler)); self.on_click = Some(Box::new(handler));
self self
@@ -708,6 +709,7 @@ impl ButtonCommon for ButtonLike {
self self
} }
#[inline(always)]
fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self { fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
self.tooltip = Some(Box::new(tooltip)); self.tooltip = Some(Box::new(tooltip));
self self
@@ -160,6 +160,7 @@ impl SelectableButton for IconButton {
} }
impl Clickable for IconButton { impl Clickable for IconButton {
#[inline(always)]
fn on_click( fn on_click(
mut self, mut self,
handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static, handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
@@ -201,6 +202,7 @@ impl ButtonCommon for IconButton {
self self
} }
#[inline(always)]
fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self { fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
self.base = self.base.tooltip(tooltip); self.base = self.base.tooltip(tooltip);
self self
+74 -71
View File
@@ -271,81 +271,14 @@ impl EventEmitter<DismissEvent> for ContextMenu {}
impl FluentBuilder for ContextMenu {} impl FluentBuilder for ContextMenu {}
impl ContextMenu { impl ContextMenu {
#[inline(always)]
pub fn new( pub fn new(
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
f: impl FnOnce(Self, &mut Window, &mut Context<Self>) -> Self, build_menu: impl FnOnce(Self, &mut Window, &mut Context<Self>) -> Self,
) -> Self { ) -> Self {
let focus_handle = cx.focus_handle(); let menu = Self::new_inner(window, cx);
let _on_blur_subscription = cx.on_blur( build_menu(menu, window, cx)
&focus_handle,
window,
|this: &mut ContextMenu, window, cx| {
if let Some(ignore_until) = this.ignore_blur_until {
if Instant::now() < ignore_until {
return;
} else {
this.ignore_blur_until = None;
}
}
if this.main_menu.is_none() {
if let SubmenuState::Open(open_submenu) = &this.submenu_state {
let submenu_focus = open_submenu.entity.read(cx).focus_handle.clone();
if submenu_focus.contains_focused(window, cx) {
return;
}
}
}
this.cancel(&menu::Cancel, window, cx)
},
);
window.refresh();
// When the menu first receives focus (i.e. when it opens), move the
// selection onto a menu item so assistive technology announces a real
// item rather than the bare menu container. Per the ARIA menu button
// pattern, opening a menu places focus on a menu item; for select-style
// menus we prefer the currently-checked item. We only do this when
// nothing is selected yet so we don't override an existing selection.
cx.on_focus_in(&focus_handle, window, |this, window, cx| {
if this.selected_index.is_none() && !this.suppress_focus_selection {
this.select_toggled_or_first(window, cx);
}
this.suppress_focus_selection = false;
})
.detach();
f(
Self {
builder: None,
items: Default::default(),
focus_handle,
action_context: None,
selected_index: None,
delayed: false,
clicked: false,
end_slot_action: None,
key_context: "menu".into(),
_on_blur_subscription,
keep_open_on_confirm: false,
fixed_width: None,
main_menu: None,
main_menu_observed_bounds: Rc::new(Cell::new(None)),
documentation_aside: None,
aside_trigger_bounds: Rc::new(RefCell::new(HashMap::default())),
submenu_state: SubmenuState::Closed,
hover_target: HoverTarget::MainMenu,
submenu_safety_threshold_x: None,
submenu_trigger_bounds: Rc::new(Cell::new(None)),
submenu_trigger_mouse_down: false,
ignore_blur_until: None,
suppress_focus_selection: false,
},
window,
cx,
)
} }
pub fn build( pub fn build(
@@ -2175,6 +2108,76 @@ impl ContextMenu {
) )
.into_any_element() .into_any_element()
} }
#[inline(never)]
fn new_inner(window: &mut Window, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
let _on_blur_subscription = cx.on_blur(
&focus_handle,
window,
|context_menu: &mut ContextMenu, window, cx| {
if let Some(ignore_until) = context_menu.ignore_blur_until {
if Instant::now() < ignore_until {
return;
} else {
context_menu.ignore_blur_until = None;
}
}
if context_menu.main_menu.is_none() {
if let SubmenuState::Open(open_submenu) = &context_menu.submenu_state {
let submenu_focus = open_submenu.entity.read(cx).focus_handle.clone();
if submenu_focus.contains_focused(window, cx) {
return;
}
}
}
context_menu.cancel(&menu::Cancel, window, cx)
},
);
window.refresh();
// When the menu first receives focus (i.e. when it opens), move the
// selection onto a menu item so assistive technology announces a real
// item rather than the bare menu container. Per the ARIA menu button
// pattern, opening a menu places focus on a menu item; for select-style
// menus we prefer the currently-checked item. We only do this when
// nothing is selected yet so we don't override an existing selection.
cx.on_focus_in(&focus_handle, window, |context_menu, window, cx| {
if context_menu.selected_index.is_none() && !context_menu.suppress_focus_selection {
context_menu.select_toggled_or_first(window, cx);
}
context_menu.suppress_focus_selection = false;
})
.detach();
Self {
builder: None,
items: Vec::new(),
focus_handle,
action_context: None,
selected_index: None,
delayed: false,
clicked: false,
end_slot_action: None,
key_context: SharedString::from("menu"),
_on_blur_subscription,
keep_open_on_confirm: false,
fixed_width: None,
main_menu: None,
main_menu_observed_bounds: Rc::new(Cell::new(None)),
documentation_aside: None,
aside_trigger_bounds: Rc::new(RefCell::new(HashMap::default())),
submenu_state: SubmenuState::Closed,
hover_target: HoverTarget::MainMenu,
submenu_safety_threshold_x: None,
submenu_trigger_bounds: Rc::new(Cell::new(None)),
submenu_trigger_mouse_down: false,
ignore_blur_until: None,
suppress_focus_selection: false,
}
}
} }
impl ContextMenuItem { impl ContextMenuItem {