repl: Push button to clear outputs (#14873)

This commit is contained in:
Kyle Kelley 2024-07-20 11:32:03 -07:00 committed by GitHub
parent a1670551bf
commit 6dfb0a4a70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 139 additions and 42 deletions

View File

@ -503,29 +503,38 @@ impl ExecutionView {
impl Render for ExecutionView { impl Render for ExecutionView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
if self.outputs.len() == 0 { if self.outputs.len() == 0 {
return match &self.status { return v_flex()
ExecutionStatus::ConnectingToKernel => { .min_h(cx.line_height())
div().child(Label::new("Connecting to kernel...").color(Color::Muted)) .justify_center()
} .child(match &self.status {
ExecutionStatus::Executing => { ExecutionStatus::ConnectingToKernel => Label::new("Connecting to kernel...")
div().child(Label::new("Executing...").color(Color::Muted)) .color(Color::Muted)
} .into_any_element(),
ExecutionStatus::Finished => div().child(Icon::new(IconName::Check)), ExecutionStatus::Executing => Label::new("Executing...")
ExecutionStatus::Unknown => { .color(Color::Muted)
div().child(div().child(Label::new("Unknown status").color(Color::Muted))) .into_any_element(),
} ExecutionStatus::Finished => Icon::new(IconName::Check)
ExecutionStatus::ShuttingDown => { .size(IconSize::Small)
div().child(Label::new("Kernel shutting down...").color(Color::Muted)) .into_any_element(),
} ExecutionStatus::Unknown => Label::new("Unknown status")
ExecutionStatus::Shutdown => { .color(Color::Muted)
div().child(Label::new("Kernel shutdown").color(Color::Muted)) .into_any_element(),
} ExecutionStatus::ShuttingDown => Label::new("Kernel shutting down...")
ExecutionStatus::Queued => div().child(Label::new("Queued").color(Color::Muted)), .color(Color::Muted)
ExecutionStatus::KernelErrored(error) => { .into_any_element(),
div().child(Label::new(format!("Kernel error: {}", error)).color(Color::Error)) ExecutionStatus::Shutdown => Label::new("Kernel shutdown")
} .color(Color::Muted)
} .into_any_element(),
.into_any_element(); ExecutionStatus::Queued => {
Label::new("Queued").color(Color::Muted).into_any_element()
}
ExecutionStatus::KernelErrored(error) => {
Label::new(format!("Kernel error: {}", error))
.color(Color::Error)
.into_any_element()
}
})
.into_any_element();
} }
div() div()

View File

@ -5,14 +5,16 @@ use crate::{
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use editor::{ use editor::{
display_map::{ display_map::{
BlockContext, BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, CustomBlockId,
RenderBlock,
}, },
scroll::Autoscroll, scroll::Autoscroll,
Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint, Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint,
}; };
use futures::{FutureExt as _, StreamExt as _}; use futures::{FutureExt as _, StreamExt as _};
use gpui::{ use gpui::{
div, prelude::*, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, WeakView, div, prelude::*, EntityId, EventEmitter, Model, Render, Subscription, Task, View, ViewContext,
WeakView,
}; };
use language::Point; use language::Point;
use project::Fs; use project::Fs;
@ -22,7 +24,7 @@ use runtimelib::{
use settings::Settings as _; use settings::Settings as _;
use std::{env::temp_dir, ops::Range, path::PathBuf, sync::Arc, time::Duration}; use std::{env::temp_dir, ops::Range, path::PathBuf, sync::Arc, time::Duration};
use theme::{ActiveTheme, ThemeSettings}; use theme::{ActiveTheme, ThemeSettings};
use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, Label}; use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, IconButtonShape, Label, Tooltip};
pub struct Session { pub struct Session {
pub editor: WeakView<Editor>, pub editor: WeakView<Editor>,
@ -39,13 +41,18 @@ struct EditorBlock {
invalidation_anchor: Anchor, invalidation_anchor: Anchor,
block_id: CustomBlockId, block_id: CustomBlockId,
execution_view: View<ExecutionView>, execution_view: View<ExecutionView>,
on_close: CloseBlockFn,
} }
type CloseBlockFn =
Arc<dyn for<'a> Fn(CustomBlockId, &'a mut WindowContext) + Send + Sync + 'static>;
impl EditorBlock { impl EditorBlock {
fn new( fn new(
editor: WeakView<Editor>, editor: WeakView<Editor>,
code_range: Range<Anchor>, code_range: Range<Anchor>,
status: ExecutionStatus, status: ExecutionStatus,
on_close: CloseBlockFn,
cx: &mut ViewContext<Session>, cx: &mut ViewContext<Session>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let execution_view = cx.new_view(|cx| ExecutionView::new(status, cx)); let execution_view = cx.new_view(|cx| ExecutionView::new(status, cx));
@ -73,7 +80,7 @@ impl EditorBlock {
position: code_range.end, position: code_range.end,
height: execution_view.num_lines(cx).saturating_add(1), height: execution_view.num_lines(cx).saturating_add(1),
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
render: Self::create_output_area_render(execution_view.clone()), render: Self::create_output_area_render(execution_view.clone(), on_close.clone()),
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
}; };
@ -87,6 +94,7 @@ impl EditorBlock {
invalidation_anchor, invalidation_anchor,
block_id, block_id,
execution_view, execution_view,
on_close,
}) })
} }
@ -98,11 +106,15 @@ impl EditorBlock {
self.editor self.editor
.update(cx, |editor, cx| { .update(cx, |editor, cx| {
let mut replacements = HashMap::default(); let mut replacements = HashMap::default();
replacements.insert( replacements.insert(
self.block_id, self.block_id,
( (
Some(self.execution_view.num_lines(cx).saturating_add(1)), Some(self.execution_view.num_lines(cx).saturating_add(1)),
Self::create_output_area_render(self.execution_view.clone()), Self::create_output_area_render(
self.execution_view.clone(),
self.on_close.clone(),
),
), ),
); );
editor.replace_blocks(replacements, None, cx); editor.replace_blocks(replacements, None, cx);
@ -110,31 +122,74 @@ impl EditorBlock {
.ok(); .ok();
} }
fn create_output_area_render(execution_view: View<ExecutionView>) -> RenderBlock { fn create_output_area_render(
execution_view: View<ExecutionView>,
on_close: CloseBlockFn,
) -> RenderBlock {
let render = move |cx: &mut BlockContext| { let render = move |cx: &mut BlockContext| {
let execution_view = execution_view.clone(); let execution_view = execution_view.clone();
let text_font = ThemeSettings::get_global(cx).buffer_font.family.clone(); let text_font = ThemeSettings::get_global(cx).buffer_font.family.clone();
let text_font_size = ThemeSettings::get_global(cx).buffer_font_size; let text_font_size = ThemeSettings::get_global(cx).buffer_font_size;
// Note: we'll want to use `cx.anchor_x` when someone runs something with no output -- just show a checkmark and not make the full block below the line
let gutter_width = cx.gutter_dimensions.width; let gutter = cx.gutter_dimensions;
let close_button_size = IconSize::XSmall;
h_flex() let block_id = cx.block_id;
let on_close = on_close.clone();
let rem_size = cx.rem_size();
let line_height = cx.text_style().line_height_in_pixels(rem_size);
let (close_button_width, close_button_padding) =
close_button_size.square_components(cx);
div()
.min_h(line_height)
.flex()
.flex_row()
.items_start()
.w_full() .w_full()
.bg(cx.theme().colors().background) .bg(cx.theme().colors().background)
.border_y_1() .border_y_1()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.pl(gutter_width) .child(
v_flex().min_h(cx.line_height()).justify_center().child(
h_flex()
.w(gutter.full_width())
.justify_end()
.pt(line_height / 2.)
.child(
h_flex()
.pr(gutter.width / 2. - close_button_width
+ close_button_padding / 2.)
.child(
IconButton::new(
("close_output_area", EntityId::from(cx.block_id)),
IconName::Close,
)
.shape(IconButtonShape::Square)
.icon_size(close_button_size)
.icon_color(Color::Muted)
.tooltip(|cx| Tooltip::text("Close output area", cx))
.on_click(
move |_, cx| {
if let BlockId::Custom(block_id) = block_id {
(on_close)(block_id, cx)
}
},
),
),
),
),
)
.child( .child(
div() div()
.flex_1()
.size_full()
.my_2()
.mr(gutter.width)
.text_size(text_font_size) .text_size(text_font_size)
.font_family(text_font) .font_family(text_font)
// .ml(gutter_width)
.mx_1()
.my_2()
.h_full()
.w_full()
.mr(gutter_width)
.child(execution_view), .child(execution_view),
) )
.into_any_element() .into_any_element()
@ -373,8 +428,30 @@ impl Session {
Kernel::Shutdown => ExecutionStatus::Shutdown, Kernel::Shutdown => ExecutionStatus::Shutdown,
}; };
let parent_message_id = message.header.msg_id.clone();
let session_view = cx.view().downgrade();
let weak_editor = self.editor.clone();
let on_close: CloseBlockFn =
Arc::new(move |block_id: CustomBlockId, cx: &mut WindowContext| {
if let Some(session) = session_view.upgrade() {
session.update(cx, |session, cx| {
session.blocks.remove(&parent_message_id);
cx.notify();
});
}
if let Some(editor) = weak_editor.upgrade() {
editor.update(cx, |editor, cx| {
let mut block_ids = HashSet::default();
block_ids.insert(block_id);
editor.remove_blocks(block_ids, None, cx);
});
}
});
let editor_block = if let Ok(editor_block) = let editor_block = if let Ok(editor_block) =
EditorBlock::new(self.editor.clone(), anchor_range, status, cx) EditorBlock::new(self.editor.clone(), anchor_range, status, on_close, cx)
{ {
editor_block editor_block
} else { } else {

View File

@ -76,8 +76,12 @@ impl IconSize {
} }
} }
/// Returns the length of a side of the square that contains this [`IconSize`], with padding. /// Returns the individual components of the square that contains this [`IconSize`].
pub(crate) fn square(&self, cx: &mut WindowContext) -> Pixels { ///
/// The returned tuple contains:
/// 1. The length of one side of the square
/// 2. The padding of one side of the square
pub fn square_components(&self, cx: &mut WindowContext) -> (Pixels, Pixels) {
let icon_size = self.rems() * cx.rem_size(); let icon_size = self.rems() * cx.rem_size();
let padding = match self { let padding = match self {
IconSize::Indicator => Spacing::None.px(cx), IconSize::Indicator => Spacing::None.px(cx),
@ -86,6 +90,13 @@ impl IconSize {
IconSize::Medium => Spacing::XSmall.px(cx), IconSize::Medium => Spacing::XSmall.px(cx),
}; };
(icon_size, padding)
}
/// Returns the length of a side of the square that contains this [`IconSize`], with padding.
pub fn square(&self, cx: &mut WindowContext) -> Pixels {
let (icon_size, padding) = self.square_components(cx);
icon_size + padding * 2. icon_size + padding * 2.
} }
} }