repl: Fix image scaling (#48435)

Continues #47114

Release Notes:

- Fixed REPL output width clamping to apply to the content area so
images don’t get clipped by controls

---------

Co-authored-by: MrSubidubi <finn@zed.dev>
This commit is contained in:
Casper van ElterenandMrSubidubi authored and GitHub committed 2026-03-04 07:33:40 +00:00
1 parent e51cd4931c
commit c0fa025bc9
6 files changed
+55 -98

No files matched your search

+29 -66
View File
@@ -1,13 +1,11 @@
#![allow(unused, dead_code)]
use std::sync::Arc;
use std::time::{Duration, Instant};
use editor::{Editor, EditorMode, MultiBuffer, SizingBehavior};
use futures::future::Shared;
use gpui::{
App, Entity, EventEmitter, Focusable, Hsla, InteractiveElement, KeyContext,
RetainAllImageCache, StatefulInteractiveElement, Task, TextStyleRefinement, image_cache,
prelude::*,
App, Entity, EventEmitter, Focusable, Hsla, InteractiveElement, RetainAllImageCache,
StatefulInteractiveElement, Task, TextStyleRefinement, prelude::*,
};
use language::{Buffer, Language, LanguageRegistry};
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
@@ -236,7 +234,7 @@ pub trait RenderableCell: Render {
fn source(&self) -> &String;
fn selected(&self) -> bool;
fn set_selected(&mut self, selected: bool) -> &mut Self;
fn selected_bg_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
fn selected_bg_color(&self, _window: &mut Window, cx: &mut Context<Self>) -> Hsla {
if self.selected() {
let mut color = cx.theme().colors().element_hover;
color.fade_out(0.5);
@@ -253,7 +251,7 @@ pub trait RenderableCell: Render {
fn cell_position_spacer(
&self,
is_first: bool,
window: &mut Window,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> {
let cell_position = self.cell_position();
@@ -328,7 +326,6 @@ pub struct MarkdownCell {
editing: bool,
selected: bool,
cell_position: Option<CellPosition>,
languages: Arc<LanguageRegistry>,
_editor_subscription: gpui::Subscription,
}
@@ -386,7 +383,6 @@ impl MarkdownCell {
let markdown = cx.new(|cx| Markdown::new(source.clone().into(), None, None, cx));
let cell_id = id.clone();
let editor_subscription =
cx.subscribe(&editor, move |this, _editor, event, cx| match event {
editor::EditorEvent::Blurred => {
@@ -410,7 +406,6 @@ impl MarkdownCell {
editing: start_editing,
selected: false,
cell_position: None,
languages,
_editor_subscription: editor_subscription,
}
}
@@ -461,8 +456,6 @@ impl MarkdownCell {
.unwrap_or_default();
self.source = source.clone();
let languages = self.languages.clone();
self.markdown.update(cx, |markdown, cx| {
markdown.reset(source.into(), cx);
});
@@ -606,7 +599,7 @@ pub struct CodeCell {
outputs: Vec<Output>,
selected: bool,
cell_position: Option<CellPosition>,
language_task: Task<()>,
_language_task: Task<()>,
execution_start_time: Option<Instant>,
execution_duration: Option<Duration>,
is_executing: bool,
@@ -670,10 +663,10 @@ impl CodeCell {
outputs: Vec::new(),
selected: false,
cell_position: None,
language_task,
execution_start_time: None,
execution_duration: None,
is_executing: false,
_language_task: language_task,
}
}
@@ -748,10 +741,10 @@ impl CodeCell {
outputs,
selected: false,
cell_position: None,
language_task,
execution_start_time: None,
execution_duration: None,
is_executing: false,
_language_task: language_task,
}
}
@@ -879,15 +872,7 @@ impl CodeCell {
cx.notify();
}
fn output_control(&self) -> Option<CellControlType> {
if self.has_outputs() {
Some(CellControlType::ClearCell)
} else {
None
}
}
pub fn gutter_output(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
pub fn gutter_output(&self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_selected = self.selected();
div()
@@ -948,7 +933,7 @@ impl RenderableCell for CodeCell {
&self.source
}
fn control(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<CellControl> {
fn control(&self, _window: &mut Window, cx: &mut Context<Self>) -> Option<CellControl> {
let control_type = if self.has_outputs() {
CellControlType::RerunCell
} else {
@@ -1038,8 +1023,7 @@ impl RenderableCell for CodeCell {
}
impl RunnableCell for CodeCell {
fn run(&mut self, window: &mut Window, cx: &mut Context<Self>) {
println!("Running code cell: {}", self.id);
fn run(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
cx.emit(CellEvent::Run(self.id.clone()));
}
@@ -1062,11 +1046,8 @@ impl Render for CodeCell {
} else {
None
};
let output_max_width = plain::max_width_for_columns(
ReplSettings::get_global(cx).output_max_width_columns,
window,
cx,
);
let output_max_width =
plain::max_width_for_columns(ReplSettings::get_global(cx).max_columns, window, cx);
// get the language from the editor's buffer
let language_name = self
.editor
@@ -1198,41 +1179,23 @@ impl Render for CodeCell {
},
)
// output at bottom
.child(div().w_full().children(self.outputs.iter().map(
|output| {
let content = match output {
Output::Plain { content, .. } => {
Some(content.clone().into_any_element())
}
Output::Markdown { content, .. } => {
Some(content.clone().into_any_element())
}
Output::Stream { content, .. } => {
Some(content.clone().into_any_element())
}
Output::Image { content, .. } => {
Some(content.clone().into_any_element())
}
Output::Message(message) => Some(
div()
.child(message.clone())
.into_any_element(),
),
Output::Table { content, .. } => {
Some(content.clone().into_any_element())
}
Output::Json { content, .. } => {
Some(content.clone().into_any_element())
}
Output::ErrorOutput(error_view) => {
error_view.render(window, cx)
}
Output::ClearOutputWaitMarker => None,
};
div().children(content)
},
))),
.child(
div()
.id((
ElementId::from(self.id.to_string()),
"output-scroll",
))
.w_full()
.when_some(output_max_width, |div, max_width| {
div.max_w(max_width).overflow_x_scroll()
})
.when_some(output_max_height, |div, max_height| {
div.max_h(max_height).overflow_y_scroll()
})
.children(self.outputs.iter().map(|output| {
div().children(output.content(window, cx))
})),
),
),
),
)
+21 -16
View File
@@ -253,18 +253,8 @@ impl Output {
)
}
pub fn render(
&self,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<ExecutionView>,
) -> impl IntoElement + use<> {
let max_width = plain::max_width_for_columns(
ReplSettings::get_global(cx).output_max_width_columns,
window,
cx,
);
let content = match self {
pub fn content(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {
match self {
Self::Plain { content, .. } => Some(content.clone().into_any_element()),
Self::Markdown { content, .. } => Some(content.clone().into_any_element()),
Self::Stream { content, .. } => Some(content.clone().into_any_element()),
@@ -274,21 +264,36 @@ impl Output {
Self::Json { content, .. } => Some(content.clone().into_any_element()),
Self::ErrorOutput(error_view) => error_view.render(window, cx),
Self::ClearOutputWaitMarker => None,
};
}
}
let needs_horizontal_scroll = matches!(self, Self::Table { .. } | Self::Image { .. });
pub fn render(
&self,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<ExecutionView>,
) -> impl IntoElement + use<> {
let max_width =
plain::max_width_for_columns(ReplSettings::get_global(cx).max_columns, window, cx);
let content = self.content(window, cx);
let needs_horizontal_scroll = matches!(self, Self::Table { .. });
h_flex()
.id("output-content")
.w_full()
.when_some(max_width, |this, max_w| this.max_w(max_w))
.overflow_x_scroll()
.when_else(
needs_horizontal_scroll,
|this| this.overflow_x_scroll(),
|this| this.overflow_x_hidden(),
)
.items_start()
.child(
div()
.when(!needs_horizontal_scroll, |el| {
el.flex_1().w_full().overflow_x_hidden()
})
.when_some(max_width, |el, max_width| el.max_w(max_width))
.children(content),
)
.children(match self {
+4 -4
View File
@@ -3,10 +3,10 @@ use base64::{
Engine as _, alphabet,
engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig},
};
use gpui::{App, ClipboardItem, Image, ImageFormat, RenderImage, Window, img};
use gpui::{App, ClipboardItem, Image, ImageFormat, Pixels, RenderImage, Window, img};
use settings::Settings as _;
use std::sync::Arc;
use ui::{IntoElement, Styled, div, prelude::*};
use ui::{IntoElement, Styled, prelude::*};
use crate::outputs::{OutputContent, plain};
use crate::repl_settings::ReplSettings;
@@ -113,7 +113,7 @@ impl Render for ImageView {
let settings = ReplSettings::get_global(cx);
let line_height = window.line_height();
let max_width = plain::max_width_for_columns(settings.output_max_width_columns, window, cx);
let max_width = plain::max_width_for_columns(settings.max_columns, window, cx);
let max_height = if settings.output_max_height_lines > 0 {
Some(line_height * settings.output_max_height_lines as f32)
@@ -125,7 +125,7 @@ impl Render for ImageView {
let image = self.image.clone();
div().h(height).w(width).child(img(image))
img(image).w(width).h(height)
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ use alacritty_terminal::{
term::Config,
vte::ansi::Processor,
};
use gpui::{Bounds, ClipboardItem, Entity, FontStyle, TextStyle, WhiteSpace, canvas, size};
use gpui::{Bounds, ClipboardItem, Entity, FontStyle, Pixels, TextStyle, WhiteSpace, canvas, size};
use language::Buffer;
use settings::Settings as _;
use terminal::terminal_settings::TerminalSettings;
-6
View File
@@ -27,11 +27,6 @@ pub struct ReplSettings {
///
/// Default: 0
pub output_max_height_lines: usize,
/// Maximum number of columns of output to display before scaling images.
/// Set to 0 to disable output width limits.
///
/// Default: 0
pub output_max_width_columns: usize,
}
impl Settings for ReplSettings {
@@ -44,7 +39,6 @@ impl Settings for ReplSettings {
inline_output: repl.inline_output.unwrap_or(true),
inline_output_max_length: repl.inline_output_max_length.unwrap_or(50),
output_max_height_lines: repl.output_max_height_lines.unwrap_or(0),
output_max_width_columns: repl.output_max_width_columns.unwrap_or(0),
}
}
}
@@ -1148,11 +1148,6 @@ pub struct ReplSettingsContent {
///
/// Default: 0
pub output_max_height_lines: Option<usize>,
/// Maximum number of columns of output to display before scaling images.
/// Set to 0 to disable output width limits.
///
/// Default: 0
pub output_max_width_columns: Option<usize>,
}
/// Settings for configuring the which-key popup behaviour.