Reduce LLVM IR bloat one last time (#63882)

I think this is the final attempt of mine to do anything about the
binary size, as those fixes become relatively intricate and I like them
less and less.

Each commit has its own improvement stats and mainly reduce generic-code
duplication through shared non-generic helpers and `#[inline(never)]`
boundaries.

Total reduction stats:

With sccache disabled, `cargo clean` and `cargo build --release -p zed`:
Before: zed binary 396716472 bytes
After: zed binary 359416504 bytes (-9.40%, -37.29MB)

Release Notes:

- N/A

---------

Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
This commit is contained in:
Kirill Bulatov 2026-09-07 15:18:49 +00:00 committed by GitHub
parent 63d15474c7
commit 20fa2fa809
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 399 additions and 218 deletions

2
Cargo.lock generated
View file

@ -7802,7 +7802,6 @@ dependencies = [
"anyhow",
"language_core",
"rust-embed",
"toml 0.8.23",
"tree-sitter",
"tree-sitter-bash",
"tree-sitter-c",
@ -14968,7 +14967,6 @@ dependencies = [
"log",
"parking_lot",
"paths",
"prost",
"release_channel",
"rpc",
"schemars 1.0.4",

View file

@ -1797,7 +1797,7 @@ impl ExtensionStore {
let config = fs.load(&language_config_path).await.with_context(|| {
format!("loading language config from {language_config_path:?}")
})?;
::toml::from_str::<LanguageConfig>(&config).map_err(anyhow::Error::from)
LanguageConfig::from_toml(&config).map_err(anyhow::Error::from)
}
};
let query_files = async {
@ -2335,7 +2335,7 @@ async fn load_plugin_language(
let config_path = language_path.join(LanguageConfig::FILE_NAME);
async move {
let contents = fs.load(&config_path).await?;
toml::from_str::<LanguageConfig>(&contents).map_err(anyhow::Error::from)
LanguageConfig::from_toml(&contents).map_err(anyhow::Error::from)
}
};
let context_provider = {

View file

@ -272,7 +272,7 @@ impl HeadlessExtensionStore {
for language_path in &manifest.languages {
let config_path = load_dir.join(language_path).join(LanguageConfig::FILE_NAME);
let config = fs.load(&config_path).await?;
let mut config = ::toml::from_str::<LanguageConfig>(&config)?;
let mut config = LanguageConfig::from_toml(&config)?;
config.grammar = None;
languages.push((config.name.clone(), config));
}

View file

@ -195,6 +195,11 @@ where
}
let mut results = segment_results.concat();
gpui_util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
rank_matches(&mut results, max_results);
results
}
#[inline(never)]
fn rank_matches(results: &mut Vec<StringMatch>, max_results: usize) {
gpui_util::truncate_to_bottom_n_sorted_by(results, max_results, &|a, b| b.cmp(a));
}

View file

@ -194,7 +194,7 @@ pub trait ParentElement {
where
Self: Sized,
{
self.extend(std::iter::once(child.into_element().into_any()));
self.extend(std::iter::once(child.into_any_element()));
self
}

View file

@ -457,42 +457,17 @@ impl<V: View> Element for ViewElement<V> {
) {
if let Some(entity_id) = self.entity_id {
// Stateful path.
window.with_rendered_view(entity_id, |window| {
let caching_disabled = window.is_inspector_picking(cx);
if self.cached_style.is_some() && !caching_disabled {
window.with_element_state::<ViewElementState, _>(
global_id.unwrap(),
|element_state, window| {
let mut element_state = element_state.unwrap();
let paint_start = window.paint_index();
if let Some(element) = element {
let refreshing = mem::replace(&mut window.refreshing, true);
element.paint(window, cx);
window.refreshing = refreshing;
} else {
window.reuse_paint(element_state.paint_range.clone());
}
let paint_end = window.paint_index();
element_state.paint_range = paint_start..paint_end;
((), element_state)
},
)
} else {
element.as_mut().unwrap().paint(window, cx);
}
});
paint_view(
entity_id,
self.cached_style.is_some(),
global_id,
element,
window,
cx,
);
} else {
// Stateless path: just paint the element.
window.with_id(
ElementId::Name(std::any::type_name::<V>().into()),
|window| {
element.as_mut().unwrap().paint(window, cx);
},
);
paint_component(std::any::type_name::<V>(), element, window, cx);
}
}
}
@ -505,3 +480,54 @@ impl Render for EmptyView {
Empty
}
}
#[inline(never)]
fn paint_view(
entity_id: EntityId,
cached: bool,
global_id: Option<&GlobalElementId>,
element: &mut Option<AnyElement>,
window: &mut Window,
cx: &mut App,
) {
window.with_rendered_view(entity_id, |window| {
let caching_disabled = window.is_inspector_picking(cx);
if cached && !caching_disabled {
window.with_element_state::<ViewElementState, _>(
global_id.unwrap(),
|element_state, window| {
let mut element_state = element_state.unwrap();
let paint_start = window.paint_index();
if let Some(element) = element {
let refreshing = mem::replace(&mut window.refreshing, true);
element.paint(window, cx);
window.refreshing = refreshing;
} else {
window.reuse_paint(element_state.paint_range.clone());
}
let paint_end = window.paint_index();
element_state.paint_range = paint_start..paint_end;
((), element_state)
},
)
} else {
element.as_mut().unwrap().paint(window, cx);
}
});
}
#[inline(never)]
fn paint_component(
name: &'static str,
element: &mut Option<AnyElement>,
window: &mut Window,
cx: &mut App,
) {
window.with_id(ElementId::Name(name.into()), |window| {
element.as_mut().unwrap().paint(window, cx);
});
}

View file

@ -17,6 +17,12 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream {
fn into_element(self) -> Self::Element {
gpui::ViewElement::new(self)
}
#[track_caller]
#[inline(never)]
fn into_any_element(self) -> gpui::AnyElement {
gpui::Element::into_any(self.into_element())
}
}
};

View file

@ -4,6 +4,7 @@
use std::{
env,
ffi::OsStr,
fmt,
ops::AddAssign,
panic::Location,
pin::Pin,
@ -251,7 +252,7 @@ where
Err(error) => {
log_error_with_caller(
*Location::caller(),
DebugAsDisplay(&error),
format_args!("{:#}", DebugAsDisplay(&error)),
log::Level::Error,
);
None
@ -277,7 +278,7 @@ where
match self {
Ok(value) => Some(value),
Err(error) => {
log_error_with_caller(*Location::caller(), error, level);
log_error_with_caller(*Location::caller(), format_args!("{error:#}"), level);
None
}
}
@ -291,10 +292,12 @@ where
}
}
fn log_error_with_caller<E>(caller: core::panic::Location<'_>, error: E, level: log::Level)
where
E: std::fmt::Display,
{
#[inline(never)]
fn log_error_with_caller(
caller: core::panic::Location<'_>,
arguments: fmt::Arguments<'_>,
level: log::Level,
) {
#[cfg(not(windows))]
let file = caller.file();
#[cfg(windows)]
@ -316,7 +319,7 @@ where
&log::Record::builder()
.target(module_path.as_deref().unwrap_or(""))
.module_path(file.as_deref())
.args(format_args!("{:#}", error))
.args(arguments)
.file(Some(caller.file()))
.line(Some(caller.line()))
.level(level)
@ -326,7 +329,11 @@ where
#[track_caller]
pub fn log_err<E: std::fmt::Display>(error: &E) {
log_error_with_caller(*Location::caller(), error, log::Level::Error);
log_error_with_caller(
*Location::caller(),
format_args!("{error:#}"),
log::Level::Error,
);
}
// Forces `{:?}` formatting through a `Display`-bounded logging helper so `anyhow::Error` emits a
@ -452,7 +459,7 @@ where
Poll::Ready(output) => Poll::Ready(match output {
Ok(output) => Some(output),
Err(error) => {
log_error_with_caller(location, error, level);
log_error_with_caller(location, format_args!("{error:#}"), level);
None
}
}),
@ -479,7 +486,11 @@ where
Poll::Ready(output) => Poll::Ready(match output {
Ok(output) => Some(output),
Err(error) => {
log_error_with_caller(location, DebugAsDisplay(&error), level);
log_error_with_caller(
location,
format_args!("{:#}", DebugAsDisplay(&error)),
level,
);
None
}
}),
@ -605,3 +616,68 @@ where
items.truncate(limit);
items.sort_by(compare);
}
#[cfg(test)]
mod logging_tests {
use super::{ResultExt, TryFutureExt, TryFutureExtBacktrace};
use log::{Level, Log, Metadata, Record};
use std::{
cell::RefCell,
future::ready,
pin::pin,
task::{Context, Poll, Waker},
};
#[test]
fn logging_preserves_diagnostics_and_callers() {
log::set_logger(&TestLogger).expect("failed to install test logger");
let error = anyhow::anyhow!("root failure")
.context("inner context")
.context("outer context");
let display = "outer context: inner context: root failure";
let debug = format!("{error:?}");
let line = line!() + 1;
assert_eq!(Err::<(), _>(&error).log_err(), None);
assert_logged(line, display);
let line = line!() + 1;
assert_eq!(Err::<(), _>(&error).log_err_with_backtrace(), None);
assert_logged(line, &debug);
let mut context = Context::from_waker(Waker::noop());
let line = line!() + 1;
let mut future = pin!(ready(Err::<(), _>(&error)).log_err());
assert_eq!(future.as_mut().poll(&mut context), Poll::Ready(None));
assert_logged(line, display);
let line = line!() + 1;
let mut future = pin!(ready(Err::<(), _>(&error)).log_err_with_backtrace());
assert_eq!(future.as_mut().poll(&mut context), Poll::Ready(None));
assert_logged(line, &debug);
}
thread_local! {
static RECORDS: RefCell<Vec<(Option<u32>, String)>> = const { RefCell::new(Vec::new()) };
}
struct TestLogger;
impl Log for TestLogger {
fn enabled(&self, _: &Metadata<'_>) -> bool {
true
}
fn log(&self, record: &Record<'_>) {
assert_eq!(record.target(), "gpui_util::lib");
assert_eq!(record.module_path(), Some("crates/gpui_util/src/lib.rs"));
assert_eq!(record.file(), Some(file!()));
assert_eq!(record.level(), Level::Error);
RECORDS.with_borrow_mut(|records| {
records.push((record.line(), record.args().to_string()));
});
}
fn flush(&self) {}
}
fn assert_logged(line: u32, message: &str) {
assert_eq!(RECORDS.take(), [(Some(line), message.to_owned())]);
}
}

View file

@ -14,7 +14,6 @@ path = "src/grammars.rs"
anyhow.workspace = true
language_core.workspace = true
rust-embed.workspace = true
toml.workspace = true
util.workspace = true
tree-sitter = { workspace = true, optional = true }

View file

@ -55,7 +55,7 @@ pub fn load_config(name: &str) -> LanguageConfig {
)
.unwrap();
let config: LanguageConfig = ::toml::from_str(&config_toml)
let config = LanguageConfig::from_toml(&config_toml)
.with_context(|| format!("failed to load config.toml for language {name:?}"))
.unwrap();

View file

@ -18,8 +18,11 @@ pub use crate::html_element::*;
pub use crate::markdown_writer::*;
/// Converts the provided HTML to Markdown.
pub fn convert_html_to_markdown(html: impl Read, handlers: &mut [TagHandler]) -> Result<String> {
let dom = parse_html(html).context("failed to parse HTML")?;
pub fn convert_html_to_markdown(
mut html: impl Read,
handlers: &mut [TagHandler],
) -> Result<String> {
let dom = parse_html(&mut html).context("failed to parse HTML")?;
let markdown_writer = MarkdownWriter::new();
let markdown = markdown_writer
@ -29,7 +32,8 @@ pub fn convert_html_to_markdown(html: impl Read, handlers: &mut [TagHandler]) ->
Ok(markdown)
}
fn parse_html(mut html: impl Read) -> Result<RcDom> {
#[inline(never)]
fn parse_html(mut html: &mut dyn Read) -> Result<RcDom> {
let parse_options = ParseOpts {
tree_builder: TreeBuilderOpts {
drop_doctype: true,

View file

@ -150,9 +150,14 @@ pub struct LanguageConfig {
impl LanguageConfig {
pub const FILE_NAME: &str = "config.toml";
#[inline(never)]
pub fn from_toml(config: &str) -> Result<Self, toml::de::Error> {
toml::from_str::<Self>(config)
}
pub fn load(config_path: impl AsRef<Path>) -> anyhow::Result<Self> {
let config = std::fs::read_to_string(config_path.as_ref())?;
toml::from_str(&config).map_err(Into::into)
Self::from_toml(&config).map_err(anyhow::Error::from)
}
}

View file

@ -1,3 +1,14 @@
mod html_minifier;
pub(crate) mod html_parser;
mod html_rendering;
use html5ever::{ParseOpts, parse_document, tendril::TendrilSink};
use markup5ever_rcdom::RcDom;
use std::io::{self, Read};
#[inline(never)]
fn parse_html(mut reader: &mut dyn Read) -> io::Result<RcDom> {
parse_document(RcDom::default(), ParseOpts::default())
.from_utf8()
.read_from(&mut reader)
}

View file

@ -1,8 +1,8 @@
use html5ever::{
Attribute, ParseOpts, QualName, parse_document,
tendril::{Tendril, TendrilSink, fmt::UTF8},
Attribute, QualName,
tendril::{Tendril, fmt::UTF8},
};
use markup5ever_rcdom::{Node, NodeData, RcDom};
use markup5ever_rcdom::{Node, NodeData};
use std::{cell::RefCell, io, rc::Rc, str};
#[derive(Default)]
@ -38,10 +38,8 @@ where
///
/// Will return `Err` if unable to write to the output writer.
#[inline]
pub fn minify<R: io::Read>(&mut self, mut r: &mut R) -> io::Result<()> {
let dom = parse_document(RcDom::default(), ParseOpts::default())
.from_utf8()
.read_from(&mut r)?;
pub fn minify(&mut self, reader: &mut dyn io::Read) -> io::Result<()> {
let dom = super::parse_html(reader)?;
if !self.options.omit_doctype {
self.w.write_all(b"<!doctype html>")?;

View file

@ -1,10 +1,8 @@
use std::{cell::RefCell, collections::HashMap, mem, ops::Range};
use gpui::{DefiniteLength, FontWeight, SharedString, TextAlign, px, relative};
use html5ever::{
Attribute, LocalName, ParseOpts, local_name, parse_document, tendril::TendrilSink,
};
use markup5ever_rcdom::{Node, NodeData, RcDom};
use html5ever::{Attribute, LocalName, local_name};
use markup5ever_rcdom::{Node, NodeData};
use pulldown_cmark::{Alignment, HeadingLevel};
use stacksafe::stacksafe;
@ -195,10 +193,7 @@ pub(crate) fn parse_html_block(
) -> Option<ParsedHtmlBlock> {
let bytes = cleanup_html(source);
let mut cursor = std::io::Cursor::new(bytes);
let dom = parse_document(RcDom::default(), ParseOpts::default())
.from_utf8()
.read_from(&mut cursor)
.ok()?;
let dom = super::parse_html(&mut cursor).ok()?;
let mut children = Vec::new();
parse_html_node(

View file

@ -820,7 +820,7 @@ pub async fn stream_response(
if line == "[DONE]" || line.is_empty() {
None
} else {
match serde_json::from_str::<StreamEvent>(line) {
match decode_stream_event(line) {
Ok(event) => Some(Ok(event)),
Err(error) => {
log::error!(
@ -974,6 +974,11 @@ pub async fn stream_response(
}
}
#[inline(never)]
fn decode_stream_event(line: &str) -> serde_json::Result<StreamEvent> {
serde_json::from_str(line)
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -69,9 +69,7 @@ pub(crate) fn try_load_shape(
return Ok(None);
};
let shape = serde_json::from_str::<PickerConfig>(&shape)
.context("Could not deserialize loaded picker shape from persistence")?
.into_centered();
let shape = parse_shape(&shape)?;
Ok(Some(Shape::HorizontallyCentered(shape)))
}
@ -90,6 +88,13 @@ pub(crate) fn load_last_preview_layout(
parse_layout(&last_layout)
}
#[inline(never)]
fn parse_shape(json: &str) -> anyhow::Result<Centered> {
Ok(serde_json::from_str::<PickerConfig>(json)
.context("Could not deserialize loaded picker shape from persistence")?
.into_centered())
}
fn shape_key(picker_delegate: &'static str, preview_layout: Option<preview::Layout>) -> String {
format!("{picker_delegate}/{}", layout_as_str(preview_layout))
}

View file

@ -19,6 +19,23 @@ include!(concat!(env!("OUT_DIR"), "/zed.messages.rs"));
pub const REMOTE_SERVER_PEER_ID: PeerId = PeerId { owner_id: 0, id: 0 };
pub const REMOTE_SERVER_PROJECT_ID: u64 = 0;
impl Envelope {
#[inline(never)]
pub fn decode_from_slice(buffer: &[u8]) -> Result<Self, DecodeError> {
Self::decode(buffer)
}
#[inline(never)]
pub fn encode_to_buffer(&self, buffer: &mut Vec<u8>) -> Result<(), prost::EncodeError> {
self.encode(buffer)
}
#[inline(never)]
pub fn encoded_size(&self) -> usize {
self.encoded_len()
}
}
messages!(
(Ack, Foreground),
(AckBufferOperation, Background),

View file

@ -30,7 +30,6 @@ gpui.workspace = true
log.workspace = true
parking_lot.workspace = true
paths.workspace = true
prost.workspace = true
release_channel.workspace = true
rpc = { workspace = true, features = ["gpui"] }
schemars.workspace = true

View file

@ -1,6 +1,5 @@
use anyhow::Result;
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use prost::Message as _;
use rpc::proto::Envelope;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
@ -20,7 +19,7 @@ pub async fn read_message_with_len<S: AsyncRead + Unpin>(
) -> Result<Envelope> {
buffer.resize(message_len as usize, 0);
stream.read_exact(buffer).await?;
Ok(Envelope::decode(buffer.as_slice())?)
Ok(Envelope::decode_from_slice(buffer.as_slice())?)
}
pub async fn read_message<S: AsyncRead + Unpin>(
@ -40,13 +39,13 @@ pub async fn write_message<S: AsyncWrite + Unpin>(
buffer: &mut Vec<u8>,
message: Envelope,
) -> Result<()> {
let message_len = message.encoded_len() as u32;
let message_len = message.encoded_size() as u32;
stream
.write_all(message_len.to_le_bytes().as_slice())
.await?;
buffer.clear();
buffer.reserve(message_len as usize);
message.encode(buffer)?;
message.encode_to_buffer(buffer)?;
stream.write_all(buffer).await?;
Ok(())
}

View file

@ -47,13 +47,13 @@ pub struct TerminalOutput {
/// Returns the default text style for the terminal output.
pub fn text_style(window: &mut Window, cx: &App) -> TextStyle {
let settings = ThemeSettings::get_global(cx).clone();
let settings = ThemeSettings::get_global(cx);
let font_size = settings.buffer_font_size(cx).into();
let font_family = settings.buffer_font.family;
let font_features = settings.buffer_font.features;
let font_family = settings.buffer_font.family.clone();
let font_features = settings.buffer_font.features.clone();
let font_weight = settings.buffer_font.weight;
let font_fallbacks = settings.buffer_font.fallbacks;
let font_fallbacks = settings.buffer_font.fallbacks.clone();
let theme = cx.theme();

View file

@ -4,7 +4,6 @@ pub use ::proto::*;
use async_tungstenite::tungstenite::Message as WebSocketMessage;
use futures::{SinkExt as _, StreamExt as _};
use proto::Message as _;
use std::time::Instant;
use std::{fmt::Debug, io};
@ -47,9 +46,9 @@ where
match message {
Message::Envelope(message) => {
self.encoding_buffer.reserve(message.encoded_len());
self.encoding_buffer.reserve(message.encoded_size());
message
.encode(&mut self.encoding_buffer)
.encode_to_buffer(&mut self.encoding_buffer)
.map_err(io::Error::from)?;
let buffer =
zstd::stream::encode_all(self.encoding_buffer.as_slice(), COMPRESSION_LEVEL)
@ -90,7 +89,7 @@ where
zstd::zstd_safe::WriteBuf::as_slice(&*bytes),
&mut self.encoding_buffer,
)?;
let envelope = Envelope::decode(self.encoding_buffer.as_slice())
let envelope = Envelope::decode_from_slice(self.encoding_buffer.as_slice())
.map_err(io::Error::from)?;
self.encoding_buffer.clear();

View file

@ -253,125 +253,8 @@ impl Peer {
}
};
let response_channels = connection_state.response_channels.clone();
let stream_response_channels = connection_state.stream_response_channels.clone();
self.connections
.write()
.insert(connection_id, connection_state);
let incoming_rx = incoming_rx.filter_map(move |(incoming, received_at)| {
let response_channels = response_channels.clone();
let stream_response_channels = stream_response_channels.clone();
async move {
let message_id = incoming.id;
tracing::trace!(?incoming, "incoming message future: start");
let _end = util::defer(move || {
tracing::trace!(%connection_id, message_id, "incoming message future: end");
});
if let Some(responding_to) = incoming.responding_to {
tracing::trace!(
%connection_id,
message_id,
responding_to,
"incoming response: received"
);
let response_channel =
response_channels.lock().as_mut()?.remove(&responding_to);
let terminal_stream_response = matches!(
&incoming.payload,
Some(proto::envelope::Payload::Error(_))
| Some(proto::envelope::Payload::EndStream(_))
);
let stream_response_channel = if terminal_stream_response {
stream_response_channels
.lock()
.as_mut()?
.remove(&responding_to)
} else {
stream_response_channels
.lock()
.as_ref()?
.get(&responding_to)
.cloned()
};
if let Some(tx) = response_channel {
let requester_resumed = oneshot::channel();
if let Err(error) = tx.send((incoming, received_at, requester_resumed.0)) {
tracing::trace!(
%connection_id,
message_id,
responding_to = responding_to,
?error,
"incoming response: request future dropped",
);
}
tracing::trace!(
%connection_id,
message_id,
responding_to,
"incoming response: waiting to resume requester"
);
let _ = requester_resumed.1.await;
tracing::trace!(
%connection_id,
message_id,
responding_to,
"incoming response: requester resumed"
);
} else if let Some(tx) = stream_response_channel {
let requester_resumed = oneshot::channel();
if let Err(error) = tx.unbounded_send((Ok(incoming), requester_resumed.0)) {
tracing::debug!(
%connection_id,
message_id,
responding_to = responding_to,
?error,
"incoming stream response: request future dropped",
);
// The consumer has gone away, so drop the bookkeeping
// for this stream rather than letting it accumulate
// every subsequent message until a terminal frame.
if let Some(channels) = stream_response_channels.lock().as_mut() {
channels.remove(&responding_to);
}
} else {
let _ = requester_resumed.1.await;
}
} else {
let message_type = proto::build_typed_envelope(
connection_id.into(),
received_at,
incoming,
)
.map(|p| p.payload_type_name());
tracing::warn!(
%connection_id,
message_id,
responding_to,
message_type,
"incoming response: unknown request"
);
}
None
} else {
tracing::trace!(%connection_id, message_id, "incoming message: received");
proto::build_typed_envelope(connection_id.into(), received_at, incoming)
.or_else(|| {
tracing::error!(
%connection_id,
message_id,
"unable to construct a typed envelope"
);
None
})
}
}
});
(connection_id, handle_io, incoming_rx.boxed())
let incoming_rx = self.register_connection(connection_id, connection_state, incoming_rx);
(connection_id, handle_io, incoming_rx)
}
#[cfg(any(test, feature = "test-support"))]
@ -704,6 +587,134 @@ impl Peer {
Ok(())
}
#[inline(never)]
fn register_connection(
&self,
connection_id: ConnectionId,
connection_state: ConnectionState,
incoming_rx: mpsc::Receiver<(proto::Envelope, Instant)>,
) -> BoxStream<'static, Box<dyn AnyTypedEnvelope>> {
let response_channels = connection_state.response_channels.clone();
let stream_response_channels = connection_state.stream_response_channels.clone();
self.connections
.write()
.insert(connection_id, connection_state);
let incoming_rx = incoming_rx.filter_map(move |(incoming, received_at)| {
let response_channels = response_channels.clone();
let stream_response_channels = stream_response_channels.clone();
async move {
let message_id = incoming.id;
tracing::trace!(?incoming, "incoming message future: start");
let _end = util::defer(move || {
tracing::trace!(%connection_id, message_id, "incoming message future: end");
});
if let Some(responding_to) = incoming.responding_to {
tracing::trace!(
%connection_id,
message_id,
responding_to,
"incoming response: received"
);
let response_channel =
response_channels.lock().as_mut()?.remove(&responding_to);
let terminal_stream_response = matches!(
&incoming.payload,
Some(proto::envelope::Payload::Error(_))
| Some(proto::envelope::Payload::EndStream(_))
);
let stream_response_channel = if terminal_stream_response {
stream_response_channels
.lock()
.as_mut()?
.remove(&responding_to)
} else {
stream_response_channels
.lock()
.as_ref()?
.get(&responding_to)
.cloned()
};
if let Some(tx) = response_channel {
let requester_resumed = oneshot::channel();
if let Err(error) = tx.send((incoming, received_at, requester_resumed.0)) {
tracing::trace!(
%connection_id,
message_id,
responding_to = responding_to,
?error,
"incoming response: request future dropped",
);
}
tracing::trace!(
%connection_id,
message_id,
responding_to,
"incoming response: waiting to resume requester"
);
let _ = requester_resumed.1.await;
tracing::trace!(
%connection_id,
message_id,
responding_to,
"incoming response: requester resumed"
);
} else if let Some(tx) = stream_response_channel {
let requester_resumed = oneshot::channel();
if let Err(error) = tx.unbounded_send((Ok(incoming), requester_resumed.0)) {
tracing::debug!(
%connection_id,
message_id,
responding_to = responding_to,
?error,
"incoming stream response: request future dropped",
);
// The consumer has gone away, so drop the bookkeeping
// for this stream rather than letting it accumulate
// every subsequent message until a terminal frame.
if let Some(channels) = stream_response_channels.lock().as_mut() {
channels.remove(&responding_to);
}
} else {
let _ = requester_resumed.1.await;
}
} else {
let message_type = proto::build_typed_envelope(
connection_id.into(),
received_at,
incoming,
)
.map(|p| p.payload_type_name());
tracing::warn!(
%connection_id,
message_id,
responding_to,
message_type,
"incoming response: unknown request"
);
}
None
} else {
tracing::trace!(%connection_id, message_id, "incoming message: received");
proto::build_typed_envelope(connection_id.into(), received_at, incoming)
.or_else(|| {
tracing::error!(
%connection_id,
message_id,
"unable to construct a typed envelope"
);
None
})
}
}
});
incoming_rx.boxed()
}
fn connection_state(&self, connection_id: ConnectionId) -> Result<ConnectionState> {
let connections = self.connections.read();
let connection = connections

View file

@ -1106,7 +1106,7 @@ impl TerminalElement {
}
fn rem_size(&self, cx: &mut App) -> Option<Pixels> {
let settings = ThemeSettings::get_global(cx).clone();
let settings = ThemeSettings::get_global(cx);
let buffer_font_size = settings.buffer_font_size(cx);
let rem_size_scale = {
// Our default UI font size is 14px on a 16px base scale.
@ -1205,7 +1205,7 @@ impl Element for TerminalElement {
cx,
|_, _, hitbox, window, cx| {
let hitbox = hitbox.unwrap();
let settings = ThemeSettings::get_global(cx).clone();
let settings = ThemeSettings::get_global(cx);
let buffer_font_size = settings.buffer_font_size(cx);

View file

@ -2,12 +2,12 @@ use std::{any::Any, fmt::Debug, ops::Not, time::Duration};
use web_time::Instant;
use gpui::{
Along, Anchor, App, AppContext as _, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask,
Context, Corners, CursorStyle, DispatchPhase, Div, Edges, Element, ElementId, Entity, EntityId,
GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero,
LayoutId, ListState, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement,
Pixels, Point, Position, Render, ScrollHandle, ScrollWheelEvent, Size, Stateful,
StatefulInteractiveElement, Style, Styled, Task, UniformListDecoration,
Along, Anchor, AnyElement, App, AppContext as _, Axis as ScrollbarAxis, BorderStyle, Bounds,
ContentMask, Context, Corners, CursorStyle, DispatchPhase, Div, Edges, Element, ElementId,
Entity, EntityId, GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement,
IntoElement, IsZero, LayoutId, ListState, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, ParentElement, Pixels, Point, Position, Render, ScrollHandle, ScrollWheelEvent,
Size, Stateful, StatefulInteractiveElement, Style, Styled, Task, UniformListDecoration,
UniformListScrollHandle, Window, ease_in_out, prelude::FluentBuilder as _, px, quad, relative,
size,
};
@ -237,7 +237,7 @@ impl<T: ScrollableHandle> UniformListDecoration for ScrollbarStateWrapper<T> {
origin: -scroll_offset,
state: self.0.clone(),
}
.into_any()
.into_any_element()
}
}
@ -934,7 +934,7 @@ impl<T: ScrollableHandle> Render for ScrollbarState<T> {
}
}
struct ScrollbarElement<T: ScrollableHandle> {
pub struct ScrollbarElement<T: ScrollableHandle> {
origin: Point<Pixels>,
state: Entity<ScrollbarState<T>>,
}
@ -954,6 +954,11 @@ impl ThumbState {
}
impl ScrollableHandle for UniformListScrollHandle {
#[inline(never)]
fn into_scrollbar_element(element: ScrollbarElement<Self>) -> AnyElement {
element.into_any()
}
fn max_offset(&self) -> Point<Pixels> {
self.0.borrow().base_handle.max_offset()
}
@ -972,6 +977,11 @@ impl ScrollableHandle for UniformListScrollHandle {
}
impl ScrollableHandle for ListState {
#[inline(never)]
fn into_scrollbar_element(element: ScrollbarElement<Self>) -> AnyElement {
element.into_any()
}
fn max_offset(&self) -> Point<Pixels> {
self.max_offset_for_scrollbar()
}
@ -998,6 +1008,11 @@ impl ScrollableHandle for ListState {
}
impl ScrollableHandle for ScrollHandle {
#[inline(never)]
fn into_scrollbar_element(element: ScrollbarElement<Self>) -> AnyElement {
element.into_any()
}
fn max_offset(&self) -> Point<Pixels> {
self.max_offset()
}
@ -1016,6 +1031,10 @@ impl ScrollableHandle for ScrollHandle {
}
pub trait ScrollableHandle: 'static + Any + Sized + Clone {
fn into_scrollbar_element(element: ScrollbarElement<Self>) -> AnyElement {
element.into_any()
}
fn max_offset(&self) -> Point<Pixels>;
fn set_offset(&self, point: Point<Pixels>);
fn offset(&self) -> Point<Pixels>;
@ -1642,6 +1661,10 @@ impl<T: ScrollableHandle> IntoElement for ScrollbarElement<T> {
fn into_element(self) -> Self::Element {
self
}
fn into_any_element(self) -> AnyElement {
T::into_scrollbar_element(self)
}
}
#[cfg(test)]