From 0bd508d517b01a508c6a5948216ecac0c1828332 Mon Sep 17 00:00:00 2001 From: matt Date: Wed, 10 Dec 2025 02:33:06 -0500 Subject: [PATCH 1/6] add new type of inlay --- crates/editor/src/display_map/inlay_map.rs | 1 + crates/editor/src/inlays.rs | 8 ++++++++ crates/project/src/project.rs | 2 ++ 3 files changed, 11 insertions(+) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index d85f761a82e2f4..0103402729b4dc 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -325,6 +325,7 @@ impl<'a> Iterator for InlayChunks<'a> { }), InlayId::Hint(_) => self.highlight_styles.inlay_hint, InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint, + InlayId::ReplResult(_) => self.highlight_styles.inlay_hint, InlayId::Color(_) => { if let InlayContent::Color(color) = inlay.content { renderer = Some(ChunkRenderer { diff --git a/crates/editor/src/inlays.rs b/crates/editor/src/inlays.rs index f07bf0b315161f..32e2b383c5503b 100644 --- a/crates/editor/src/inlays.rs +++ b/crates/editor/src/inlays.rs @@ -104,6 +104,14 @@ impl Inlay { } } + pub fn repl_result>(id: usize, position: Anchor, text: T) -> Self { + Self { + id: InlayId::ReplResult(id), + position, + content: InlayContent::Text(text.into()), + } + } + pub fn text(&self) -> &Rope { static COLOR_TEXT: OnceLock = OnceLock::new(); match &self.content { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f1060ee2560c82..f3e0bf5f839311 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -413,6 +413,7 @@ pub enum InlayId { // LSP Hint(usize), Color(usize), + ReplResult(usize), } impl InlayId { @@ -422,6 +423,7 @@ impl InlayId { Self::DebuggerValue(id) => *id, Self::Hint(id) => *id, Self::Color(id) => *id, + Self::ReplResult(id) => *id, } } } From 47b8411c0f8baa1741c09734fb02ca1f94e74dfe Mon Sep 17 00:00:00 2001 From: matt Date: Wed, 10 Dec 2025 04:13:49 -0500 Subject: [PATCH 2/6] represults can be displayed inline in a cute manner --- crates/editor/src/display_map/inlay_map.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 0103402729b4dc..e28f89d9a0f5e2 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -325,7 +325,27 @@ impl<'a> Iterator for InlayChunks<'a> { }), InlayId::Hint(_) => self.highlight_styles.inlay_hint, InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint, - InlayId::ReplResult(_) => self.highlight_styles.inlay_hint, + InlayId::ReplResult(_) => { + let text = inlay.text().to_string(); + renderer = Some(ChunkRenderer { + id: ChunkRendererId::Inlay(inlay.id), + render: Arc::new(move |cx| { + let colors = cx.theme().colors(); + div() + .ml_2() + .px_1() + .rounded_sm() + .bg(colors.surface_background) + .text_color(colors.text_muted) + .text_xs() + .child(text.trim().to_string()) + .into_any_element() + }), + constrain_width: false, + measured_width: None, + }); + self.highlight_styles.inlay_hint + } InlayId::Color(_) => { if let InlayContent::Color(color) = inlay.content { renderer = Some(ChunkRenderer { From 1e6d0839cb16dc834fcfbd6fa11a4bfe5a3fa3c4 Mon Sep 17 00:00:00 2001 From: matt Date: Wed, 10 Dec 2025 04:19:07 -0500 Subject: [PATCH 3/6] spacing --- crates/editor/src/display_map/inlay_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index e28f89d9a0f5e2..6152e85ca42f23 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -332,7 +332,7 @@ impl<'a> Iterator for InlayChunks<'a> { render: Arc::new(move |cx| { let colors = cx.theme().colors(); div() - .ml_2() + .ml_4() .px_1() .rounded_sm() .bg(colors.surface_background) From 770030b337a5f2b5abbd7d89a2b3adb4adf5c095 Mon Sep 17 00:00:00 2001 From: matt Date: Wed, 10 Dec 2025 04:31:02 -0500 Subject: [PATCH 4/6] working prototype --- crates/editor/src/display_map/inlay_map.rs | 20 +- crates/editor/src/editor.rs | 56 +++++ crates/editor/src/element.rs | 96 ++++++++ crates/repl/src/outputs.rs | 51 ++++- crates/repl/src/repl_editor.rs | 102 +++++++++ crates/repl/src/repl_settings.rs | 11 + crates/repl/src/session.rs | 244 ++++++++++++++++++++- crates/settings/src/settings_content.rs | 9 + 8 files changed, 570 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 6152e85ca42f23..34d797b152220d 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -332,13 +332,19 @@ impl<'a> Iterator for InlayChunks<'a> { render: Arc::new(move |cx| { let colors = cx.theme().colors(); div() - .ml_4() - .px_1() - .rounded_sm() - .bg(colors.surface_background) - .text_color(colors.text_muted) - .text_xs() - .child(text.trim().to_string()) + .flex() + .flex_row() + .items_center() + .child(div().w_4()) + .child( + div() + .px_1() + .rounded_sm() + .bg(colors.surface_background) + .text_color(colors.text_muted) + .text_xs() + .child(text.trim().to_string()), + ) .into_any_element() }), constrain_width: false, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d841bf858b8a77..dc6f0bda230334 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -732,6 +732,15 @@ type BackgroundHighlight = ( ); type GutterHighlight = (fn(&App) -> Hsla, Vec>); +#[derive(Clone)] +pub struct GutterAnnotation { + pub position: Anchor, //row + pub label: SharedString, + pub color: fn(&App) -> Hsla, +} + +type GutterAnnotations = Vec; + #[derive(Default)] struct ScrollbarMarkerState { scrollbar_size: Size, @@ -1084,6 +1093,7 @@ pub struct Editor { highlighted_rows: HashMap>, background_highlights: HashMap, gutter_highlights: HashMap, + gutter_annotations: HashMap, scrollbar_marker_state: ScrollbarMarkerState, active_indent_guides_state: ActiveIndentGuidesState, nav_history: Option, @@ -2250,6 +2260,7 @@ impl Editor { highlighted_rows: HashMap::default(), background_highlights: HashMap::default(), gutter_highlights: HashMap::default(), + gutter_annotations: HashMap::default(), scrollbar_marker_state: ScrollbarMarkerState::default(), active_indent_guides_state: ActiveIndentGuidesState::default(), nav_history: None, @@ -21490,6 +21501,51 @@ impl Editor { .insert(TypeId::of::(), (color_fetcher, gutter_highlights)); } + pub fn insert_gutter_annotation( + &mut self, + position: Anchor, + label: impl Into, + color: fn(&App) -> Hsla, + cx: &mut Context, + ) { + let annotation = GutterAnnotation { + position, + label: label.into(), + color, + }; + let annotations = self + .gutter_annotations + .entry(TypeId::of::()) + .or_default(); + annotations.push(annotation); + cx.notify(); + } + + pub fn remove_gutter_annotations( + &mut self, + positions_to_remove: Vec, + cx: &mut Context, + ) { + let snapshot = self.buffer().read(cx).snapshot(cx); + if let Some(annotations) = self.gutter_annotations.get_mut(&TypeId::of::()) { + annotations.retain(|annotation| { + !positions_to_remove.iter().any(|pos| { + annotation.position.cmp(pos, &snapshot) == Ordering::Equal + }) + }); + cx.notify(); + } + } + + pub fn clear_gutter_annotations(&mut self, cx: &mut Context) { + self.gutter_annotations.remove(&TypeId::of::()); + cx.notify(); + } + + pub fn gutter_annotations(&self) -> impl Iterator { + self.gutter_annotations.values().flatten() + } + #[cfg(feature = "test-support")] pub fn all_text_highlights( &self, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fab51cbef29de4..af17a5714c9aae 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2769,6 +2769,71 @@ impl EditorElement { Some(shaped_lines) } + fn layout_gutter_annotations( + &self, + buffer_rows: &[RowInfo], + scroll_position: gpui::Point, + line_height: Pixels, + gutter_hitbox: &Hitbox, + _gutter_dimensions: &GutterDimensions, + snapshot: &EditorSnapshot, + window: &mut Window, + cx: &mut App, + ) -> Option> { + let annotations: Vec<_> = self + .editor + .read(cx) + .gutter_annotations() + .cloned() + .collect(); + + if annotations.is_empty() { + return None; + } + + let scroll_top = scroll_position.y * ScrollPixelOffset::from(line_height); + let buffer_snapshot = snapshot.display_snapshot.buffer_snapshot(); + + let elements = buffer_rows + .iter() + .enumerate() + .filter_map(|(ix, row_info)| { + let buffer_row = row_info.buffer_row?; + let annotation = annotations.iter().find(|a| { + let annotation_point = a.position.to_point(&buffer_snapshot); + annotation_point.row == buffer_row + })?; + + let color = (annotation.color)(cx); + let mut element = div() + .h(line_height) + .flex() + .items_center() + .text_ui_xs(cx) + .text_color(color) + .child(annotation.label.clone()) + .into_any_element(); + + let start_y = ix as f32 * line_height + - Pixels::from(scroll_top % ScrollPixelOffset::from(line_height)); + let gutter_strip_width = Self::gutter_strip_width(line_height); + let start_x = gutter_strip_width + px(6.); + let absolute_offset = gutter_hitbox.origin + point(start_x, start_y); + + element.prepaint_as_root( + absolute_offset, + size(AvailableSpace::MaxContent, AvailableSpace::MinContent), + window, + cx, + ); + + Some(element) + }) + .collect(); + + Some(elements) + } + fn layout_indent_guides( &self, content_origin: gpui::Point, @@ -6492,6 +6557,23 @@ impl EditorElement { }) } + fn paint_gutter_annotations( + &self, + layout: &mut EditorLayout, + window: &mut Window, + cx: &mut App, + ) { + let Some(gutter_annotation_elements) = layout.gutter_annotation_elements.take() else { + return; + }; + + window.paint_layer(layout.gutter_hitbox.bounds, |window| { + for mut element in gutter_annotation_elements.into_iter() { + element.paint(window, cx); + } + }) + } + fn paint_text(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { window.with_content_mask( Some(ContentMask { @@ -9879,6 +9961,17 @@ impl Element for EditorElement { cx, ); + let gutter_annotation_elements = self.layout_gutter_annotations( + &row_infos, + scroll_position, + line_height, + &gutter_hitbox, + &gutter_dimensions, + &snapshot, + window, + cx, + ); + let line_elements = self.prepaint_lines( start_row, &mut line_layouts, @@ -10199,6 +10292,7 @@ impl Element for EditorElement { line_elements, line_numbers, blamed_display_rows, + gutter_annotation_elements, inline_diagnostics, inline_blame_layout, inline_code_actions, @@ -10265,6 +10359,7 @@ impl Element for EditorElement { if layout.gutter_hitbox.size.width > Pixels::ZERO { self.paint_blamed_display_rows(layout, window, cx); + self.paint_gutter_annotations(layout, window, cx); self.paint_line_numbers(layout, window, cx); } @@ -10375,6 +10470,7 @@ pub struct EditorLayout { line_numbers: Arc>, display_hunks: Vec<(DisplayDiffHunk, Option)>, blamed_display_rows: Option>, + gutter_annotation_elements: Option>, inline_diagnostics: HashMap, inline_blame_layout: Option, inline_code_actions: Option, diff --git a/crates/repl/src/outputs.rs b/crates/repl/src/outputs.rs index b99562393a2bba..68b73b67bb4772 100644 --- a/crates/repl/src/outputs.rs +++ b/crates/repl/src/outputs.rs @@ -34,7 +34,7 @@ //! interpreting and displaying various types of Jupyter output. use editor::{Editor, MultiBuffer}; -use gpui::{AnyElement, ClipboardItem, Entity, Render, WeakEntity}; +use gpui::{AnyElement, ClipboardItem, Entity, EventEmitter, Render, WeakEntity}; use language::Buffer; use runtimelib::{ExecutionState, JupyterMessageContent, MimeBundle, MimeType}; use ui::{ @@ -58,6 +58,9 @@ pub(crate) mod user_error; use user_error::ErrorView; use workspace::Workspace; +use crate::repl_settings::ReplSettings; +use settings::Settings; + /// When deciding what to render from a collection of mediatypes, we need to rank them in order of importance fn rank_mime_type(mimetype: &MimeType) -> usize { match mimetype { @@ -389,6 +392,9 @@ pub enum ExecutionStatus { Restarting, } +pub struct ExecutionViewFinishedEmpty; +pub struct ExecutionViewFinishedSmall(pub String); + /// An ExecutionView shows the outputs of an execution. /// It can hold zero or more outputs, which the user /// sees as "the output" for a single execution. @@ -399,6 +405,9 @@ pub struct ExecutionView { pub status: ExecutionStatus, } +impl EventEmitter for ExecutionView {} +impl EventEmitter for ExecutionView {} + impl ExecutionView { pub fn new( status: ExecutionStatus, @@ -475,7 +484,16 @@ impl ExecutionView { ExecutionState::Busy => { self.status = ExecutionStatus::Executing; } - ExecutionState::Idle => self.status = ExecutionStatus::Finished, + ExecutionState::Idle => { + self.status = ExecutionStatus::Finished; + if self.outputs.is_empty() { + cx.emit(ExecutionViewFinishedEmpty); + } else if ReplSettings::get_global(cx).inline_output { + if let Some(small_text) = self.get_small_inline_output(cx) { + cx.emit(ExecutionViewFinishedSmall(small_text)); + } + } + } ExecutionState::Unknown => self.status = ExecutionStatus::Unknown, ExecutionState::Starting => self.status = ExecutionStatus::ConnectingToKernel, ExecutionState::Restarting => self.status = ExecutionStatus::Restarting, @@ -527,6 +545,35 @@ impl ExecutionView { } } + /// Check if the output is a single small plain text that can be shown inline. + /// Returns the text if it's suitable for inline display (single line, short enough). + fn get_small_inline_output(&self, cx: &App) -> Option { + // Only consider single outputs + if self.outputs.len() != 1 { + return None; + } + + let output = self.outputs.first()?; + + // Only Plain outputs can be inlined + let content = match output { + Output::Plain { content, .. } => content, + _ => return None, + }; + + let text = content.read(cx).full_text(); + let trimmed = text.trim(); + + let max_length = ReplSettings::get_global(cx).inline_output_max_length; + + // Must be a single line and within the configured max length + if trimmed.contains('\n') || trimmed.len() > max_length { + return None; + } + + Some(trimmed.to_string()) + } + fn apply_terminal_text( &mut self, text: &str, diff --git a/crates/repl/src/repl_editor.rs b/crates/repl/src/repl_editor.rs index 9e52637ab75c02..b2921bdce58f5a 100644 --- a/crates/repl/src/repl_editor.rs +++ b/crates/repl/src/repl_editor.rs @@ -433,6 +433,36 @@ fn runnable_ranges( } let snippet_range = cell_range(buffer, range.start.row, range.end.row); + + // Check if the snippet range is entirely blank, if so, skip forward to find code + let is_blank = (snippet_range.start.row..=snippet_range.end.row) + .all(|row| buffer.is_line_blank(row)); + + if is_blank { + // Search forward for the next non-blank line + let max_row = buffer.max_point().row; + let mut next_row = snippet_range.end.row + 1; + while next_row <= max_row && buffer.is_line_blank(next_row) { + next_row += 1; + } + + if next_row <= max_row { + // Found a non-blank line, find the extent of this cell + let next_snippet_range = cell_range(buffer, next_row, next_row); + let start_language = buffer.language_at(next_snippet_range.start); + let end_language = buffer.language_at(next_snippet_range.end); + + if start_language + .zip(end_language) + .is_some_and(|(start, end)| start == end) + { + return (vec![next_snippet_range], None); + } + } + + return (Vec::new(), None); + } + let start_language = buffer.language_at(snippet_range.start); let end_language = buffer.language_at(snippet_range.end); @@ -821,4 +851,76 @@ mod tests { },] ); } + + #[gpui::test] + fn test_skip_blank_lines_to_next_cell(cx: &mut App) { + let test_language = Arc::new(Language::new( + LanguageConfig { + name: "TestLang".into(), + line_comments: vec!["# ".into()], + ..Default::default() + }, + None, + )); + + let buffer = cx.new(|cx| { + Buffer::local( + indoc! { r#" + print(1 + 1) + + print(2 + 2) + "# }, + cx, + ) + .with_language(test_language.clone(), cx) + }); + let snapshot = buffer.read(cx).snapshot(); + + // Selection on blank line should skip to next non-blank cell + let (snippets, _) = runnable_ranges(&snapshot, Point::new(1, 0)..Point::new(1, 0), cx); + let snippets = snippets + .into_iter() + .map(|range| snapshot.text_for_range(range).collect::()) + .collect::>(); + assert_eq!(snippets, vec!["print(2 + 2)"]); + + // Multiple blank lines should also skip forward + let buffer = cx.new(|cx| { + Buffer::local( + indoc! { r#" + print(1 + 1) + + + + print(2 + 2) + "# }, + cx, + ) + .with_language(test_language.clone(), cx) + }); + let snapshot = buffer.read(cx).snapshot(); + + let (snippets, _) = runnable_ranges(&snapshot, Point::new(2, 0)..Point::new(2, 0), cx); + let snippets = snippets + .into_iter() + .map(|range| snapshot.text_for_range(range).collect::()) + .collect::>(); + assert_eq!(snippets, vec!["print(2 + 2)"]); + + // Blank lines at end of file should return nothing + let buffer = cx.new(|cx| { + Buffer::local( + indoc! { r#" + print(1 + 1) + + "# }, + cx, + ) + .with_language(test_language, cx) + }); + let snapshot = buffer.read(cx).snapshot(); + + let (snippets, _) = runnable_ranges(&snapshot, Point::new(1, 0)..Point::new(1, 0), cx); + assert!(snippets.is_empty()); + } } diff --git a/crates/repl/src/repl_settings.rs b/crates/repl/src/repl_settings.rs index 9faed72e557dd2..b7fb0672e1a0ee 100644 --- a/crates/repl/src/repl_settings.rs +++ b/crates/repl/src/repl_settings.rs @@ -13,6 +13,15 @@ pub struct ReplSettings { /// /// Default: 128 pub max_columns: usize, + /// Whether to show small single-line outputs inline instead of in a block. + /// + /// Default: true + pub inline_output: bool, + /// Maximum number of characters for an output to be shown inline. + /// Only applies when `inline_output` is true. + /// + /// Default: 50 + pub inline_output_max_length: usize, } impl Settings for ReplSettings { @@ -22,6 +31,8 @@ impl Settings for ReplSettings { Self { max_lines: repl.max_lines.unwrap(), max_columns: repl.max_columns.unwrap(), + inline_output: repl.inline_output.unwrap_or(true), + inline_output_max_length: repl.inline_output_max_length.unwrap_or(50), } } } diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 1fa0bfec356c43..c61deea19d8ad8 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -4,19 +4,25 @@ use crate::setup_editor_session_actions; use crate::{ KernelStatus, kernels::{Kernel, KernelSpecification, NativeRunningKernel}, - outputs::{ExecutionStatus, ExecutionView}, + outputs::{ExecutionStatus, ExecutionView, ExecutionViewFinishedEmpty, ExecutionViewFinishedSmall}, }; use anyhow::Context as _; use collections::{HashMap, HashSet}; use editor::SelectionEffects; use editor::{ - Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint, + Anchor, AnchorRangeExt as _, Editor, Inlay, MultiBuffer, ToOffset, ToPoint, display_map::{ BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, }, scroll::Autoscroll, }; +use project::InlayId; + +/// Marker types +enum ReplExecutedRange {} +enum ReplExecutionNumber {} + use futures::FutureExt as _; use gpui::{ Context, Entity, EventEmitter, Render, Subscription, Task, WeakEntity, Window, div, prelude::*, @@ -36,9 +42,15 @@ pub struct Session { fs: Arc, editor: WeakEntity, pub kernel: Kernel, - blocks: HashMap, pub kernel_specification: KernelSpecification, - _buffer_subscription: Subscription, + + blocks: HashMap, + result_inlays: HashMap, usize)>, + execution_annotations: HashMap, + next_inlay_id: usize, + execution_counter: usize, + + _subscriptions: Vec, } struct EditorBlock { @@ -220,8 +232,12 @@ impl Session { editor, kernel: Kernel::StartingKernel(Task::ready(()).shared()), blocks: HashMap::default(), + result_inlays: HashMap::default(), + execution_annotations: HashMap::default(), + next_inlay_id: 0, + execution_counter: 0, kernel_specification, - _buffer_subscription: subscription, + _subscriptions: vec![subscription], }; session.start_kernel(window, cx); @@ -321,20 +337,70 @@ impl Session { let snapshot = buffer.read(cx).snapshot(cx); let mut blocks_to_remove: HashSet = HashSet::default(); + let mut gutter_ranges_to_remove: Vec> = Vec::new(); + let mut keys_to_remove: Vec = Vec::new(); - self.blocks.retain(|_id, block| { + self.blocks.retain(|id, block| { if block.invalidation_anchor.is_valid(&snapshot) { true } else { blocks_to_remove.insert(block.block_id); + gutter_ranges_to_remove.push(block.code_range.clone()); + keys_to_remove.push(id.clone()); false } }); - if !blocks_to_remove.is_empty() { + let mut inlays_to_remove: Vec = Vec::new(); + + self.result_inlays + .retain(|id, (inlay_id, code_range, original_len)| { + let start_offset = code_range.start.to_offset(&snapshot); + let end_offset = code_range.end.to_offset(&snapshot); + let current_len = end_offset.saturating_sub(start_offset); + + if current_len != *original_len { + inlays_to_remove.push(*inlay_id); + gutter_ranges_to_remove.push(code_range.clone()); + keys_to_remove.push(id.clone()); + false + } else { + true + } + }); + + let mut annotation_positions_to_remove: Vec = Vec::new(); + for key in &keys_to_remove { + if let Some(position) = self.execution_annotations.remove(key) { + annotation_positions_to_remove.push(position); + } + } + + if !blocks_to_remove.is_empty() + || !inlays_to_remove.is_empty() + || !gutter_ranges_to_remove.is_empty() + || !annotation_positions_to_remove.is_empty() + { self.editor .update(cx, |editor, cx| { - editor.remove_blocks(blocks_to_remove, None, cx); + if !blocks_to_remove.is_empty() { + editor.remove_blocks(blocks_to_remove, None, cx); + } + if !inlays_to_remove.is_empty() { + editor.splice_inlays(&inlays_to_remove, vec![], cx); + } + if !gutter_ranges_to_remove.is_empty() { + editor.remove_gutter_highlights::( + gutter_ranges_to_remove, + cx, + ); + } + if !annotation_positions_to_remove.is_empty() { + editor.remove_gutter_annotations::( + annotation_positions_to_remove, + cx, + ); + } }) .ok(); cx.notify(); @@ -350,17 +416,70 @@ impl Session { anyhow::Ok(()) } + fn replace_block_with_inlay(&mut self, message_id: &str, text: &str, cx: &mut Context) { + let Some(block) = self.blocks.remove(message_id) else { + return; + }; + + let Some(editor) = self.editor.upgrade() else { + return; + }; + + let code_range = block.code_range.clone(); + + editor.update(cx, |editor, cx| { + let mut block_ids = HashSet::default(); + block_ids.insert(block.block_id); + editor.remove_blocks(block_ids, None, cx); + + let buffer = editor.buffer().read(cx).snapshot(cx); + let start_offset = code_range.start.to_offset(&buffer); + let end_offset = code_range.end.to_offset(&buffer); + let original_len = end_offset.saturating_sub(start_offset); + + let end_point = code_range.end.to_point(&buffer); + let inlay_position = buffer.anchor_after(end_point); + + let inlay_id = self.next_inlay_id; + self.next_inlay_id += 1; + + let inlay = Inlay::repl_result(inlay_id, inlay_position, format!(" {}", text)); + + editor.splice_inlays(&[], vec![inlay], cx); + self.result_inlays.insert( + message_id.to_string(), + (InlayId::ReplResult(inlay_id), code_range.clone(), original_len), + ); + + editor.insert_gutter_highlight::( + code_range, + |cx| cx.theme().status().success, + cx, + ); + }); + + cx.notify(); + } + pub fn clear_outputs(&mut self, cx: &mut Context) { let blocks_to_remove: HashSet = self.blocks.values().map(|block| block.block_id).collect(); + let inlays_to_remove: Vec = + self.result_inlays.values().map(|(id, _, _)| *id).collect(); + self.editor .update(cx, |editor, cx| { editor.remove_blocks(blocks_to_remove, None, cx); + editor.splice_inlays(&inlays_to_remove, vec![], cx); + editor.clear_gutter_highlights::(cx); + editor.clear_gutter_annotations::(cx); }) .ok(); self.blocks.clear(); + self.result_inlays.clear(); + self.execution_annotations.clear(); } pub fn execute( @@ -388,21 +507,65 @@ impl Session { let message: JupyterMessage = execute_request.into(); let mut blocks_to_remove: HashSet = HashSet::default(); + let mut inlays_to_remove: Vec = Vec::new(); + let mut gutter_ranges_to_remove: Vec> = Vec::new(); let buffer = editor.read(cx).buffer().read(cx).snapshot(cx); - self.blocks.retain(|_key, block| { + let mut block_keys_to_remove: Vec = Vec::new(); + + self.blocks.retain(|key, block| { if anchor_range.overlaps(&block.code_range, &buffer) { blocks_to_remove.insert(block.block_id); + block_keys_to_remove.push(key.clone()); false } else { true } }); + let mut result_inlay_keys_to_remove: Vec = Vec::new(); + + self.result_inlays.retain(|key, (inlay_id, inlay_range, _)| { + if anchor_range.overlaps(inlay_range, &buffer) { + inlays_to_remove.push(*inlay_id); + gutter_ranges_to_remove.push(inlay_range.clone()); + result_inlay_keys_to_remove.push(key.clone()); + false + } else { + true + } + }); + + // Remove execution annotations for both result_inlays and blocks being replaced + let mut annotation_positions_to_remove: Vec = Vec::new(); + for key in result_inlay_keys_to_remove + .iter() + .chain(block_keys_to_remove.iter()) + { + if let Some(position) = self.execution_annotations.remove(key) { + annotation_positions_to_remove.push(position); + } + } + self.editor .update(cx, |editor, cx| { editor.remove_blocks(blocks_to_remove, None, cx); + if !inlays_to_remove.is_empty() { + editor.splice_inlays(&inlays_to_remove, vec![], cx); + } + if !gutter_ranges_to_remove.is_empty() { + editor.remove_gutter_highlights::( + gutter_ranges_to_remove, + cx, + ); + } + if !annotation_positions_to_remove.is_empty() { + editor.remove_gutter_annotations::( + annotation_positions_to_remove, + cx, + ); + } }) .ok(); @@ -418,12 +581,18 @@ impl Session { let parent_message_id = message.header.msg_id.clone(); let session_view = cx.entity().downgrade(); let weak_editor = self.editor.clone(); + let code_range_for_close = anchor_range.clone(); let on_close: CloseBlockFn = Arc::new( move |block_id: CustomBlockId, _: &mut Window, cx: &mut App| { + let mut annotation_position_to_remove: Option = None; + if let Some(session) = session_view.upgrade() { session.update(cx, |session, cx| { session.blocks.remove(&parent_message_id); + // Also remove the execution annotation + annotation_position_to_remove = + session.execution_annotations.remove(&parent_message_id); cx.notify(); }); } @@ -433,23 +602,78 @@ impl Session { let mut block_ids = HashSet::default(); block_ids.insert(block_id); editor.remove_blocks(block_ids, None, cx); + editor.remove_gutter_highlights::( + vec![code_range_for_close.clone()], + cx, + ); + // Also remove the execution annotation if it exists + if let Some(position) = annotation_position_to_remove { + editor.remove_gutter_annotations::( + vec![position], + cx, + ); + } }); } }, ); let Ok(editor_block) = - EditorBlock::new(self.editor.clone(), anchor_range, status, on_close, cx) + EditorBlock::new(self.editor.clone(), anchor_range.clone(), status, on_close, cx) else { return; }; + // Increment execution counter and add execution number annotation + self.execution_counter += 1; + let execution_number = self.execution_counter; + + self.editor + .update(cx, |editor, cx| { + // Add gutter highlight for the executed range + editor.insert_gutter_highlight::( + anchor_range.clone(), + |cx| cx.theme().status().success, + cx, + ); + + // Add execution number annotation at the end of the cell + editor.insert_gutter_annotation::( + anchor_range.end, + format!("[{}]", execution_number), + |cx| cx.theme().status().success, + cx, + ); + }) + .ok(); + + self.execution_annotations + .insert(message.header.msg_id.clone(), anchor_range.end); + let new_cursor_pos = if let Some(next_cursor) = next_cell { next_cursor } else { editor_block.invalidation_anchor }; + let msg_id = message.header.msg_id.clone(); + let subscription = cx.subscribe( + &editor_block.execution_view, + move |session, _execution_view, _event: &ExecutionViewFinishedEmpty, cx| { + session.replace_block_with_inlay(&msg_id, "✓", cx); + }, + ); + self._subscriptions.push(subscription); + + let msg_id = message.header.msg_id.clone(); + let subscription = cx.subscribe( + &editor_block.execution_view, + move |session, _execution_view, event: &ExecutionViewFinishedSmall, cx| { + session.replace_block_with_inlay(&msg_id, &event.0, cx); + }, + ); + self._subscriptions.push(subscription); + self.blocks .insert(message.header.msg_id.clone(), editor_block); diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 230e1ffd48b9cc..6a0fb25deee8be 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -952,6 +952,15 @@ pub struct ReplSettingsContent { /// /// Default: 128 pub max_columns: Option, + /// Whether to show small single-line outputs inline instead of in a block. + /// + /// Default: true + pub inline_output: Option, + /// Maximum number of characters for an output to be shown inline. + /// Only applies when `inline_output` is true. + /// + /// Default: 50 + pub inline_output_max_length: Option, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] From 547c1dd16c9a5a49cdb686ef2f997148960806b8 Mon Sep 17 00:00:00 2001 From: matt Date: Wed, 10 Dec 2025 21:43:18 -0500 Subject: [PATCH 5/6] stylistic changes --- crates/editor/src/editor.rs | 6 ++--- crates/editor/src/element.rs | 7 +---- crates/repl/src/repl_editor.rs | 4 +-- crates/repl/src/session.rs | 47 ++++++++++++++++++++-------------- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index dc6f0bda230334..f00d17bdf2a19a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -21529,9 +21529,9 @@ impl Editor { let snapshot = self.buffer().read(cx).snapshot(cx); if let Some(annotations) = self.gutter_annotations.get_mut(&TypeId::of::()) { annotations.retain(|annotation| { - !positions_to_remove.iter().any(|pos| { - annotation.position.cmp(pos, &snapshot) == Ordering::Equal - }) + !positions_to_remove + .iter() + .any(|pos| annotation.position.cmp(pos, &snapshot) == Ordering::Equal) }); cx.notify(); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index af17a5714c9aae..e87763ef648858 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2780,12 +2780,7 @@ impl EditorElement { window: &mut Window, cx: &mut App, ) -> Option> { - let annotations: Vec<_> = self - .editor - .read(cx) - .gutter_annotations() - .cloned() - .collect(); + let annotations: Vec<_> = self.editor.read(cx).gutter_annotations().cloned().collect(); if annotations.is_empty() { return None; diff --git a/crates/repl/src/repl_editor.rs b/crates/repl/src/repl_editor.rs index b2921bdce58f5a..85bd8bfb4a070f 100644 --- a/crates/repl/src/repl_editor.rs +++ b/crates/repl/src/repl_editor.rs @@ -435,8 +435,8 @@ fn runnable_ranges( let snippet_range = cell_range(buffer, range.start.row, range.end.row); // Check if the snippet range is entirely blank, if so, skip forward to find code - let is_blank = (snippet_range.start.row..=snippet_range.end.row) - .all(|row| buffer.is_line_blank(row)); + let is_blank = + (snippet_range.start.row..=snippet_range.end.row).all(|row| buffer.is_line_blank(row)); if is_blank { // Search forward for the next non-blank line diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index c61deea19d8ad8..2c114fdedb996a 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -4,7 +4,9 @@ use crate::setup_editor_session_actions; use crate::{ KernelStatus, kernels::{Kernel, KernelSpecification, NativeRunningKernel}, - outputs::{ExecutionStatus, ExecutionView, ExecutionViewFinishedEmpty, ExecutionViewFinishedSmall}, + outputs::{ + ExecutionStatus, ExecutionView, ExecutionViewFinishedEmpty, ExecutionViewFinishedSmall, + }, }; use anyhow::Context as _; use collections::{HashMap, HashSet}; @@ -448,7 +450,11 @@ impl Session { editor.splice_inlays(&[], vec![inlay], cx); self.result_inlays.insert( message_id.to_string(), - (InlayId::ReplResult(inlay_id), code_range.clone(), original_len), + ( + InlayId::ReplResult(inlay_id), + code_range.clone(), + original_len, + ), ); editor.insert_gutter_highlight::( @@ -526,16 +532,17 @@ impl Session { let mut result_inlay_keys_to_remove: Vec = Vec::new(); - self.result_inlays.retain(|key, (inlay_id, inlay_range, _)| { - if anchor_range.overlaps(inlay_range, &buffer) { - inlays_to_remove.push(*inlay_id); - gutter_ranges_to_remove.push(inlay_range.clone()); - result_inlay_keys_to_remove.push(key.clone()); - false - } else { - true - } - }); + self.result_inlays + .retain(|key, (inlay_id, inlay_range, _)| { + if anchor_range.overlaps(inlay_range, &buffer) { + inlays_to_remove.push(*inlay_id); + gutter_ranges_to_remove.push(inlay_range.clone()); + result_inlay_keys_to_remove.push(key.clone()); + false + } else { + true + } + }); // Remove execution annotations for both result_inlays and blocks being replaced let mut annotation_positions_to_remove: Vec = Vec::new(); @@ -555,10 +562,8 @@ impl Session { editor.splice_inlays(&inlays_to_remove, vec![], cx); } if !gutter_ranges_to_remove.is_empty() { - editor.remove_gutter_highlights::( - gutter_ranges_to_remove, - cx, - ); + editor + .remove_gutter_highlights::(gutter_ranges_to_remove, cx); } if !annotation_positions_to_remove.is_empty() { editor.remove_gutter_annotations::( @@ -618,9 +623,13 @@ impl Session { }, ); - let Ok(editor_block) = - EditorBlock::new(self.editor.clone(), anchor_range.clone(), status, on_close, cx) - else { + let Ok(editor_block) = EditorBlock::new( + self.editor.clone(), + anchor_range.clone(), + status, + on_close, + cx, + ) else { return; }; From 36197f1504b4130b43cab65e2da92abdbcfbf04f Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 11 Dec 2025 00:47:33 -0500 Subject: [PATCH 6/6] strip gutter annotations for the purposes of merging repl stuff faster --- crates/editor/src/editor.rs | 56 ---------------------- crates/editor/src/element.rs | 91 ------------------------------------ crates/repl/src/session.rs | 76 +----------------------------- 3 files changed, 2 insertions(+), 221 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f00d17bdf2a19a..d841bf858b8a77 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -732,15 +732,6 @@ type BackgroundHighlight = ( ); type GutterHighlight = (fn(&App) -> Hsla, Vec>); -#[derive(Clone)] -pub struct GutterAnnotation { - pub position: Anchor, //row - pub label: SharedString, - pub color: fn(&App) -> Hsla, -} - -type GutterAnnotations = Vec; - #[derive(Default)] struct ScrollbarMarkerState { scrollbar_size: Size, @@ -1093,7 +1084,6 @@ pub struct Editor { highlighted_rows: HashMap>, background_highlights: HashMap, gutter_highlights: HashMap, - gutter_annotations: HashMap, scrollbar_marker_state: ScrollbarMarkerState, active_indent_guides_state: ActiveIndentGuidesState, nav_history: Option, @@ -2260,7 +2250,6 @@ impl Editor { highlighted_rows: HashMap::default(), background_highlights: HashMap::default(), gutter_highlights: HashMap::default(), - gutter_annotations: HashMap::default(), scrollbar_marker_state: ScrollbarMarkerState::default(), active_indent_guides_state: ActiveIndentGuidesState::default(), nav_history: None, @@ -21501,51 +21490,6 @@ impl Editor { .insert(TypeId::of::(), (color_fetcher, gutter_highlights)); } - pub fn insert_gutter_annotation( - &mut self, - position: Anchor, - label: impl Into, - color: fn(&App) -> Hsla, - cx: &mut Context, - ) { - let annotation = GutterAnnotation { - position, - label: label.into(), - color, - }; - let annotations = self - .gutter_annotations - .entry(TypeId::of::()) - .or_default(); - annotations.push(annotation); - cx.notify(); - } - - pub fn remove_gutter_annotations( - &mut self, - positions_to_remove: Vec, - cx: &mut Context, - ) { - let snapshot = self.buffer().read(cx).snapshot(cx); - if let Some(annotations) = self.gutter_annotations.get_mut(&TypeId::of::()) { - annotations.retain(|annotation| { - !positions_to_remove - .iter() - .any(|pos| annotation.position.cmp(pos, &snapshot) == Ordering::Equal) - }); - cx.notify(); - } - } - - pub fn clear_gutter_annotations(&mut self, cx: &mut Context) { - self.gutter_annotations.remove(&TypeId::of::()); - cx.notify(); - } - - pub fn gutter_annotations(&self) -> impl Iterator { - self.gutter_annotations.values().flatten() - } - #[cfg(feature = "test-support")] pub fn all_text_highlights( &self, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e87763ef648858..fab51cbef29de4 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2769,66 +2769,6 @@ impl EditorElement { Some(shaped_lines) } - fn layout_gutter_annotations( - &self, - buffer_rows: &[RowInfo], - scroll_position: gpui::Point, - line_height: Pixels, - gutter_hitbox: &Hitbox, - _gutter_dimensions: &GutterDimensions, - snapshot: &EditorSnapshot, - window: &mut Window, - cx: &mut App, - ) -> Option> { - let annotations: Vec<_> = self.editor.read(cx).gutter_annotations().cloned().collect(); - - if annotations.is_empty() { - return None; - } - - let scroll_top = scroll_position.y * ScrollPixelOffset::from(line_height); - let buffer_snapshot = snapshot.display_snapshot.buffer_snapshot(); - - let elements = buffer_rows - .iter() - .enumerate() - .filter_map(|(ix, row_info)| { - let buffer_row = row_info.buffer_row?; - let annotation = annotations.iter().find(|a| { - let annotation_point = a.position.to_point(&buffer_snapshot); - annotation_point.row == buffer_row - })?; - - let color = (annotation.color)(cx); - let mut element = div() - .h(line_height) - .flex() - .items_center() - .text_ui_xs(cx) - .text_color(color) - .child(annotation.label.clone()) - .into_any_element(); - - let start_y = ix as f32 * line_height - - Pixels::from(scroll_top % ScrollPixelOffset::from(line_height)); - let gutter_strip_width = Self::gutter_strip_width(line_height); - let start_x = gutter_strip_width + px(6.); - let absolute_offset = gutter_hitbox.origin + point(start_x, start_y); - - element.prepaint_as_root( - absolute_offset, - size(AvailableSpace::MaxContent, AvailableSpace::MinContent), - window, - cx, - ); - - Some(element) - }) - .collect(); - - Some(elements) - } - fn layout_indent_guides( &self, content_origin: gpui::Point, @@ -6552,23 +6492,6 @@ impl EditorElement { }) } - fn paint_gutter_annotations( - &self, - layout: &mut EditorLayout, - window: &mut Window, - cx: &mut App, - ) { - let Some(gutter_annotation_elements) = layout.gutter_annotation_elements.take() else { - return; - }; - - window.paint_layer(layout.gutter_hitbox.bounds, |window| { - for mut element in gutter_annotation_elements.into_iter() { - element.paint(window, cx); - } - }) - } - fn paint_text(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { window.with_content_mask( Some(ContentMask { @@ -9956,17 +9879,6 @@ impl Element for EditorElement { cx, ); - let gutter_annotation_elements = self.layout_gutter_annotations( - &row_infos, - scroll_position, - line_height, - &gutter_hitbox, - &gutter_dimensions, - &snapshot, - window, - cx, - ); - let line_elements = self.prepaint_lines( start_row, &mut line_layouts, @@ -10287,7 +10199,6 @@ impl Element for EditorElement { line_elements, line_numbers, blamed_display_rows, - gutter_annotation_elements, inline_diagnostics, inline_blame_layout, inline_code_actions, @@ -10354,7 +10265,6 @@ impl Element for EditorElement { if layout.gutter_hitbox.size.width > Pixels::ZERO { self.paint_blamed_display_rows(layout, window, cx); - self.paint_gutter_annotations(layout, window, cx); self.paint_line_numbers(layout, window, cx); } @@ -10465,7 +10375,6 @@ pub struct EditorLayout { line_numbers: Arc>, display_hunks: Vec<(DisplayDiffHunk, Option)>, blamed_display_rows: Option>, - gutter_annotation_elements: Option>, inline_diagnostics: HashMap, inline_blame_layout: Option, inline_code_actions: Option, diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 2c114fdedb996a..337b3714362914 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -23,7 +23,6 @@ use project::InlayId; /// Marker types enum ReplExecutedRange {} -enum ReplExecutionNumber {} use futures::FutureExt as _; use gpui::{ @@ -48,9 +47,7 @@ pub struct Session { blocks: HashMap, result_inlays: HashMap, usize)>, - execution_annotations: HashMap, next_inlay_id: usize, - execution_counter: usize, _subscriptions: Vec, } @@ -235,9 +232,7 @@ impl Session { kernel: Kernel::StartingKernel(Task::ready(()).shared()), blocks: HashMap::default(), result_inlays: HashMap::default(), - execution_annotations: HashMap::default(), next_inlay_id: 0, - execution_counter: 0, kernel_specification, _subscriptions: vec![subscription], }; @@ -371,17 +366,9 @@ impl Session { } }); - let mut annotation_positions_to_remove: Vec = Vec::new(); - for key in &keys_to_remove { - if let Some(position) = self.execution_annotations.remove(key) { - annotation_positions_to_remove.push(position); - } - } - if !blocks_to_remove.is_empty() || !inlays_to_remove.is_empty() || !gutter_ranges_to_remove.is_empty() - || !annotation_positions_to_remove.is_empty() { self.editor .update(cx, |editor, cx| { @@ -397,12 +384,6 @@ impl Session { cx, ); } - if !annotation_positions_to_remove.is_empty() { - editor.remove_gutter_annotations::( - annotation_positions_to_remove, - cx, - ); - } }) .ok(); cx.notify(); @@ -479,13 +460,11 @@ impl Session { editor.remove_blocks(blocks_to_remove, None, cx); editor.splice_inlays(&inlays_to_remove, vec![], cx); editor.clear_gutter_highlights::(cx); - editor.clear_gutter_annotations::(cx); }) .ok(); self.blocks.clear(); self.result_inlays.clear(); - self.execution_annotations.clear(); } pub fn execute( @@ -518,43 +497,26 @@ impl Session { let buffer = editor.read(cx).buffer().read(cx).snapshot(cx); - let mut block_keys_to_remove: Vec = Vec::new(); - - self.blocks.retain(|key, block| { + self.blocks.retain(|_key, block| { if anchor_range.overlaps(&block.code_range, &buffer) { blocks_to_remove.insert(block.block_id); - block_keys_to_remove.push(key.clone()); false } else { true } }); - let mut result_inlay_keys_to_remove: Vec = Vec::new(); - self.result_inlays - .retain(|key, (inlay_id, inlay_range, _)| { + .retain(|_key, (inlay_id, inlay_range, _)| { if anchor_range.overlaps(inlay_range, &buffer) { inlays_to_remove.push(*inlay_id); gutter_ranges_to_remove.push(inlay_range.clone()); - result_inlay_keys_to_remove.push(key.clone()); false } else { true } }); - // Remove execution annotations for both result_inlays and blocks being replaced - let mut annotation_positions_to_remove: Vec = Vec::new(); - for key in result_inlay_keys_to_remove - .iter() - .chain(block_keys_to_remove.iter()) - { - if let Some(position) = self.execution_annotations.remove(key) { - annotation_positions_to_remove.push(position); - } - } - self.editor .update(cx, |editor, cx| { editor.remove_blocks(blocks_to_remove, None, cx); @@ -565,12 +527,6 @@ impl Session { editor .remove_gutter_highlights::(gutter_ranges_to_remove, cx); } - if !annotation_positions_to_remove.is_empty() { - editor.remove_gutter_annotations::( - annotation_positions_to_remove, - cx, - ); - } }) .ok(); @@ -590,14 +546,9 @@ impl Session { let on_close: CloseBlockFn = Arc::new( move |block_id: CustomBlockId, _: &mut Window, cx: &mut App| { - let mut annotation_position_to_remove: Option = None; - if let Some(session) = session_view.upgrade() { session.update(cx, |session, cx| { session.blocks.remove(&parent_message_id); - // Also remove the execution annotation - annotation_position_to_remove = - session.execution_annotations.remove(&parent_message_id); cx.notify(); }); } @@ -611,13 +562,6 @@ impl Session { vec![code_range_for_close.clone()], cx, ); - // Also remove the execution annotation if it exists - if let Some(position) = annotation_position_to_remove { - editor.remove_gutter_annotations::( - vec![position], - cx, - ); - } }); } }, @@ -633,32 +577,16 @@ impl Session { return; }; - // Increment execution counter and add execution number annotation - self.execution_counter += 1; - let execution_number = self.execution_counter; - self.editor .update(cx, |editor, cx| { - // Add gutter highlight for the executed range editor.insert_gutter_highlight::( anchor_range.clone(), |cx| cx.theme().status().success, cx, ); - - // Add execution number annotation at the end of the cell - editor.insert_gutter_annotation::( - anchor_range.end, - format!("[{}]", execution_number), - |cx| cx.theme().status().success, - cx, - ); }) .ok(); - self.execution_annotations - .insert(message.header.msg_id.clone(), anchor_range.end); - let new_cursor_pos = if let Some(next_cursor) = next_cell { next_cursor } else {