diff --git a/Cargo.lock b/Cargo.lock index 2406785e67..ea108192ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8664,6 +8664,7 @@ dependencies = [ "image", "language", "log", + "multi_buffer", "project", "runtimelib", "schemars", diff --git a/crates/multi_buffer/src/anchor.rs b/crates/multi_buffer/src/anchor.rs index ff900ca066..03e21b8570 100644 --- a/crates/multi_buffer/src/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -131,11 +131,7 @@ impl AnchorRangeExt for Range { } fn overlaps(&self, other: &Range, buffer: &MultiBufferSnapshot) -> bool { - let start_cmp = self.start.cmp(&other.start, buffer); - let end_cmp = self.end.cmp(&other.end, buffer); - - (start_cmp == Ordering::Less || start_cmp == Ordering::Equal) - && (end_cmp == Ordering::Greater || end_cmp == Ordering::Equal) + self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le() } fn to_offset(&self, content: &MultiBufferSnapshot) -> Range { diff --git a/crates/repl/Cargo.toml b/crates/repl/Cargo.toml index 87b70121d8..0d8af819f0 100644 --- a/crates/repl/Cargo.toml +++ b/crates/repl/Cargo.toml @@ -24,6 +24,7 @@ futures.workspace = true image.workspace = true language.workspace = true log.workspace = true +multi_buffer.workspace = true project.workspace = true runtimelib.workspace = true schemars.workspace = true diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 4db0c73d0b..cedd624d2c 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -7,10 +7,13 @@ use editor::{ display_map::{ BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, }, - Anchor, AnchorRangeExt as _, Editor, + Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint, }; use futures::{FutureExt as _, StreamExt as _}; -use gpui::{div, prelude::*, EventEmitter, Render, Task, View, ViewContext, WeakView}; +use gpui::{ + div, prelude::*, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, WeakView, +}; +use language::Point; use project::Fs; use runtimelib::{ ExecuteRequest, InterruptRequest, JupyterMessage, JupyterMessageContent, KernelInfoRequest, @@ -27,11 +30,13 @@ pub struct Session { blocks: HashMap, pub messaging_task: Task<()>, pub kernel_specification: KernelSpecification, + _buffer_subscription: Subscription, } struct EditorBlock { editor: WeakView, code_range: Range, + invalidation_anchor: Anchor, block_id: BlockId, execution_view: View, } @@ -45,7 +50,25 @@ impl EditorBlock { ) -> anyhow::Result { let execution_view = cx.new_view(|cx| ExecutionView::new(status, cx)); - let block_id = editor.update(cx, |editor, cx| { + let (block_id, invalidation_anchor) = editor.update(cx, |editor, cx| { + let buffer = editor.buffer().clone(); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let end_point = code_range.end.to_point(&buffer_snapshot); + let next_row_start = end_point + Point::new(1, 0); + if next_row_start > buffer_snapshot.max_point() { + buffer.update(cx, |buffer, cx| { + buffer.edit( + [( + buffer_snapshot.max_point()..buffer_snapshot.max_point(), + "\n", + )], + None, + cx, + ) + }); + } + + let invalidation_anchor = buffer.read(cx).read(cx).anchor_before(next_row_start); let block = BlockProperties { position: code_range.end, height: execution_view.num_lines(cx).saturating_add(1), @@ -54,12 +77,14 @@ impl EditorBlock { disposition: BlockDisposition::Below, }; - editor.insert_blocks([block], None, cx)[0] + let block_id = editor.insert_blocks([block], None, cx)[0]; + (block_id, invalidation_anchor) })?; anyhow::Ok(Self { editor, code_range, + invalidation_anchor, block_id, execution_view, }) @@ -179,15 +204,55 @@ impl Session { }) .shared(); + let subscription = match editor.upgrade() { + Some(editor) => { + let buffer = editor.read(cx).buffer().clone(); + cx.subscribe(&buffer, Self::on_buffer_event) + } + None => Subscription::new(|| {}), + }; + return Self { editor, kernel: Kernel::StartingKernel(pending_kernel), messaging_task: Task::ready(()), blocks: HashMap::default(), kernel_specification, + _buffer_subscription: subscription, }; } + fn on_buffer_event( + &mut self, + buffer: Model, + event: &multi_buffer::Event, + cx: &mut ViewContext, + ) { + if let multi_buffer::Event::Edited { .. } = event { + let snapshot = buffer.read(cx).snapshot(cx); + + let mut blocks_to_remove: HashSet = HashSet::default(); + + self.blocks.retain(|_id, block| { + if block.invalidation_anchor.is_valid(&snapshot) { + true + } else { + blocks_to_remove.insert(block.block_id); + false + } + }); + + if !blocks_to_remove.is_empty() { + self.editor + .update(cx, |editor, cx| { + editor.remove_blocks(blocks_to_remove, None, cx); + }) + .ok(); + cx.notify(); + } + } + } + fn send(&mut self, message: JupyterMessage, _cx: &mut ViewContext) -> anyhow::Result<()> { match &mut self.kernel { Kernel::RunningKernel(kernel) => {