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,
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 async_channel;
use collections::{HashMap, HashSet};
@@ -667,23 +669,39 @@ fn connect_client_future(
dispatch_tx: mpsc::UnboundedSender<ForegroundWork>,
connection_tx: futures::channel::oneshot::Sender<ConnectionTo<Agent>>,
) -> 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.
// The SDK requires the closure to be `Send`, so we move a clone of
// `dispatch_tx` into each one.
macro_rules! on_request {
($handler:ident) => {{
let dispatch_tx = dispatch_tx.clone();
async move |req, responder, _connection| {
enqueue_request(&dispatch_tx, req, responder, $handler);
let dispatch_sender = dispatch_sender.clone();
async move |request, responder, _connection| {
enqueue_request(&dispatch_sender, request, responder, $handler);
Ok(())
}
}};
}
macro_rules! on_notification {
($handler:ident) => {{
let dispatch_tx = dispatch_tx.clone();
async move |notif, _connection| {
enqueue_notification(&dispatch_tx, notif, $handler);
let dispatch_sender = dispatch_sender.clone();
async move |notification, _connection| {
enqueue_notification(&dispatch_sender, notification, $handler);
Ok(())
}
}};
@@ -738,16 +756,6 @@ fn connect_client_future(
on_notification!(handle_complete_elicitation),
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(
+42 -22
View File
@@ -66,24 +66,10 @@ impl Editor {
state.changed |= changed;
return result;
}
let mut state = 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(),
},
};
let mut state = self.prepare_selection_effects(effects);
let (changed, result) = self.selections.change_with(&snapshot, change);
state.changed = state.changed || changed;
if self.defer_selection_effects {
self.deferred_selection_effects_state = Some(state);
} else {
self.apply_selection_effects(state, window, cx);
}
self.apply_selection_effects(state, window, cx);
result
}
@@ -119,12 +105,7 @@ impl Editor {
let already_deferred = self.defer_selection_effects;
self.defer_selection_effects = true;
let result = update(self, window, cx);
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);
}
}
self.finish_deferred_selection_effects(already_deferred, window, cx);
result
}
@@ -1776,12 +1757,51 @@ impl Editor {
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(
&mut self,
state: DeferredSelectionEffectsState,
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.defer_selection_effects {
self.deferred_selection_effects_state = Some(state);
return;
}
if state.changed {
self.selection_history.push(state.history_entry);
+55 -44
View File
@@ -58,7 +58,9 @@
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 crate::shape::{Centered, PositionAndShape, Shape, SizeBounds};
@@ -430,50 +432,31 @@ impl<D: PickerDelegate> Picker<D> {
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
div()
.id(S::id())
.absolute()
.cursor(side.cursor())
.map(|this| {
side.position(
this,
self.shape.clamped_position_and_size(
self.preview_layout_rendered(window),
&self.size_bounds,
window,
),
render_resize_handle(
side,
self.shape,
&self.size_bounds,
self.preview_layout_rendered(window),
window,
)
.on_drag_move::<ResizeDrag<S>>(cx.listener(
move |picker, 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,
&picker.size_bounds,
picker.preview_layout_rendered(window),
window,
)
})
.block_mouse_except_scroll()
.on_mouse_down(MouseButton::Left, do_nothing)
.on_drag(
ResizeDrag::<S>::start_new(
self.shape,
&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)
}))
);
picker.shape = Shape::Resizing(working);
cx.notify();
},
))
.on_click(cx.listener(move |picker, event: &ClickEvent, window, cx| {
picker.reset_size_to_default_on_double_click(side, event, window, cx)
}))
}
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) {
window.prevent_default();
cx.stop_propagation();
@@ -1,4 +1,4 @@
use std::cell::RefCell;
use std::{cell::RefCell, fmt::Display};
use serde::Deserialize;
@@ -50,14 +50,22 @@ where
{
match T::deserialize(deserializer) {
Ok(value) => Ok(value),
Err(e) => ERRORS.with_borrow_mut(|errors| {
if let Some(errors) = errors {
errors.push(anyhow::anyhow!("{}", e));
Ok(Default::default())
} else {
Err(e)
Err(error) => {
let mut error = Some(error);
let mut fallback = None;
recover_deserialization_error(&mut |record_error| {
if let Some(error) = error.take() {
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)
}
#[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)]
mod tests {
use serde::Deserialize;
+18 -3
View File
@@ -4694,6 +4694,20 @@ fn update_settings_file(
window: &mut Window,
cx: &mut 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<()> {
telemetry::event!("Settings Change", setting = file_name, type = file.setting_type());
@@ -4708,7 +4722,7 @@ fn update_settings_file(
}
SettingsUiFile::User => {
// 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(())
}
SettingsUiFile::Server(_) => unimplemented!(),
@@ -4833,10 +4847,11 @@ impl ProjectSettingsUpdateQueue {
}
}
#[inline(never)]
fn update_project_setting_file(
worktree_id: WorktreeId,
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>,
cx: &mut App,
) -> Result<()> {
@@ -4857,7 +4872,7 @@ fn update_project_setting_file(
settings_window: settings_window.downgrade(),
project: project.downgrade(),
worktree: worktree.downgrade(),
update: Box::new(update),
update: update(),
};
ProjectSettingsUpdateQueue::enqueue(cx, entry);
@@ -314,6 +314,7 @@ impl Disableable for Button {
}
impl Clickable for Button {
#[inline(always)]
fn on_click(
mut self,
handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
@@ -393,6 +394,7 @@ impl ButtonCommon for Button {
/// Button::new("tooltip_button", "Hover Me")
/// .tooltip(Tooltip::text("This is a tooltip"));
/// ```
#[inline(always)]
fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
self.base = self.base.tooltip(tooltip);
self
@@ -670,6 +670,7 @@ impl SelectableButton for ButtonLike {
}
impl Clickable for ButtonLike {
#[inline(always)]
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Box::new(handler));
self
@@ -708,6 +709,7 @@ impl ButtonCommon for ButtonLike {
self
}
#[inline(always)]
fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
self.tooltip = Some(Box::new(tooltip));
self
@@ -160,6 +160,7 @@ impl SelectableButton for IconButton {
}
impl Clickable for IconButton {
#[inline(always)]
fn on_click(
mut self,
handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
@@ -201,6 +202,7 @@ impl ButtonCommon for IconButton {
self
}
#[inline(always)]
fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
self.base = self.base.tooltip(tooltip);
self
+74 -71
View File
@@ -271,81 +271,14 @@ impl EventEmitter<DismissEvent> for ContextMenu {}
impl FluentBuilder for ContextMenu {}
impl ContextMenu {
#[inline(always)]
pub fn new(
window: &mut Window,
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 {
let focus_handle = cx.focus_handle();
let _on_blur_subscription = cx.on_blur(
&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,
)
let menu = Self::new_inner(window, cx);
build_menu(menu, window, cx)
}
pub fn build(
@@ -2175,6 +2108,76 @@ impl ContextMenu {
)
.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 {