From a7df04c375c993cd1b5671e43aad3b4be085ff4e Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Thu, 19 Feb 2026 23:56:29 -0800 Subject: [PATCH] repl: Add clear output(s) command (#49631) Closes #15947 This adds `repl:ClearCurrentOutput` and `repl:ClearOutputs` commands. No keybindings are set for this. Just an action people can bind. Release Notes: - Added ability to clear outputs by action --- crates/repl/src/repl.rs | 2 +- crates/repl/src/repl_editor.rs | 34 +++++++++++++++++++++- crates/repl/src/repl_sessions_ui.rs | 2 ++ crates/repl/src/session.rs | 45 +++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/crates/repl/src/repl.rs b/crates/repl/src/repl.rs index be64973e52d4750e4dbb17f944507f245711774b..f17cf8dfba5f5e0e950bd5f2967a6b20d2eebb51 100644 --- a/crates/repl/src/repl.rs +++ b/crates/repl/src/repl.rs @@ -20,7 +20,7 @@ pub use crate::jupyter_settings::JupyterSettings; pub use crate::kernels::{Kernel, KernelSpecification, KernelStatus, PythonEnvKernelSpecification}; pub use crate::repl_editor::*; pub use crate::repl_sessions_ui::{ - ClearOutputs, Interrupt, ReplSessionsPage, Restart, Run, Sessions, Shutdown, + ClearCurrentOutput, ClearOutputs, Interrupt, ReplSessionsPage, Restart, Run, Sessions, Shutdown, }; pub use crate::repl_settings::ReplSettings; pub use crate::repl_store::ReplStore; diff --git a/crates/repl/src/repl_editor.rs b/crates/repl/src/repl_editor.rs index 6733170f4348eaf2af70da3f190ceebdf84df44d..dacc20b70a6fac439cd9f9e999d0b22f291d98ba 100644 --- a/crates/repl/src/repl_editor.rs +++ b/crates/repl/src/repl_editor.rs @@ -14,7 +14,8 @@ use crate::kernels::PythonEnvKernelSpecification; use crate::repl_store::ReplStore; use crate::session::SessionEvent; use crate::{ - ClearOutputs, Interrupt, JupyterSettings, KernelSpecification, Restart, Session, Shutdown, + ClearCurrentOutput, ClearOutputs, Interrupt, JupyterSettings, KernelSpecification, Restart, + Session, Shutdown, }; pub fn assign_kernelspec( @@ -349,6 +350,24 @@ pub fn clear_outputs(editor: WeakEntity, cx: &mut App) { }); } +pub fn clear_current_output(editor: WeakEntity, cx: &mut App) { + let Some(editor_entity) = editor.upgrade() else { + return; + }; + + let store = ReplStore::global(cx); + let entity_id = editor.entity_id(); + let Some(session) = store.read(cx).get_session(entity_id).cloned() else { + return; + }; + + let position = editor_entity.read(cx).selections.newest_anchor().head(); + + session.update(cx, |session, cx| { + session.clear_output_at_position(position, cx); + }); +} + pub fn interrupt(editor: WeakEntity, cx: &mut App) { let store = ReplStore::global(cx); let entity_id = editor.entity_id(); @@ -410,6 +429,19 @@ pub fn setup_editor_session_actions(editor: &mut Editor, editor_handle: WeakEnti }) .detach(); + editor + .register_action({ + let editor_handle = editor_handle.clone(); + move |_: &ClearCurrentOutput, _, cx| { + if !JupyterSettings::enabled(cx) { + return; + } + + crate::clear_current_output(editor_handle.clone(), cx); + } + }) + .detach(); + editor .register_action({ let editor_handle = editor_handle.clone(); diff --git a/crates/repl/src/repl_sessions_ui.rs b/crates/repl/src/repl_sessions_ui.rs index f798e61798a708752c9bdc0286817e9d8a9f636d..af5fa460ee5be1b9e85c5cd4b8fd495d7710dc88 100644 --- a/crates/repl/src/repl_sessions_ui.rs +++ b/crates/repl/src/repl_sessions_ui.rs @@ -21,6 +21,8 @@ actions!( RunInPlace, /// Clears all outputs in the REPL. ClearOutputs, + /// Clears the output of the cell at the current cursor position. + ClearCurrentOutput, /// Opens the REPL sessions panel. Sessions, /// Interrupts the currently running kernel. diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index b939dfedc230a32e554bc5ff379f879143e788d1..fd81f65b2f021326d43852e932bce76b39735738 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -514,6 +514,51 @@ impl Session { self.result_inlays.clear(); } + pub fn clear_output_at_position(&mut self, position: Anchor, cx: &mut Context) { + let Some(editor) = self.editor.upgrade() else { + return; + }; + + let (block_id, code_range, msg_id) = { + let snapshot = editor.read(cx).buffer().read(cx).read(cx); + let pos_range = position..position; + + let block_to_remove = self + .blocks + .iter() + .find(|(_, block)| block.code_range.includes(&pos_range, &snapshot)); + + let Some((msg_id, block)) = block_to_remove else { + return; + }; + + (block.block_id, block.code_range.clone(), msg_id.clone()) + }; + + let inlay_to_remove = self.result_inlays.get(&msg_id).map(|(id, _, _)| *id); + + self.blocks.remove(&msg_id); + if inlay_to_remove.is_some() { + self.result_inlays.remove(&msg_id); + } + + self.editor + .update(cx, |editor, cx| { + let mut block_ids = HashSet::default(); + block_ids.insert(block_id); + editor.remove_blocks(block_ids, None, cx); + + if let Some(inlay_id) = inlay_to_remove { + editor.splice_inlays(&[inlay_id], vec![], cx); + } + + editor.remove_gutter_highlights::(vec![code_range], cx); + }) + .ok(); + + cx.notify(); + } + pub fn execute( &mut self, code: String,