Invalidate anchors when they get deleted (#14116)

Allows deleting the outputs directly within the editor. This also fixes
the overlap logic to make sure that the ends and the starts are
compared.


https://github.com/zed-industries/zed/assets/836375/84f5f582-95f3-4c6a-a3c9-54da6009e34d

Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
This commit is contained in:
Kyle Kelley 2024-07-11 11:21:41 -07:00 committed by GitHub
parent 018a2a29ea
commit e51d469025
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 72 additions and 9 deletions

1
Cargo.lock generated
View file

@ -8664,6 +8664,7 @@ dependencies = [
"image",
"language",
"log",
"multi_buffer",
"project",
"runtimelib",
"schemars",

View file

@ -131,11 +131,7 @@ impl AnchorRangeExt for Range<Anchor> {
}
fn overlaps(&self, other: &Range<Anchor>, 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<usize> {

View file

@ -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

View file

@ -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<String, EditorBlock>,
pub messaging_task: Task<()>,
pub kernel_specification: KernelSpecification,
_buffer_subscription: Subscription,
}
struct EditorBlock {
editor: WeakView<Editor>,
code_range: Range<Anchor>,
invalidation_anchor: Anchor,
block_id: BlockId,
execution_view: View<ExecutionView>,
}
@ -45,7 +50,25 @@ impl EditorBlock {
) -> anyhow::Result<Self> {
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<MultiBuffer>,
event: &multi_buffer::Event,
cx: &mut ViewContext<Self>,
) {
if let multi_buffer::Event::Edited { .. } = event {
let snapshot = buffer.read(cx).snapshot(cx);
let mut blocks_to_remove: HashSet<BlockId> = 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<Self>) -> anyhow::Result<()> {
match &mut self.kernel {
Kernel::RunningKernel(kernel) => {