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:
parent
018a2a29ea
commit
e51d469025
4 changed files with 72 additions and 9 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -8664,6 +8664,7 @@ dependencies = [
|
|||
"image",
|
||||
"language",
|
||||
"log",
|
||||
"multi_buffer",
|
||||
"project",
|
||||
"runtimelib",
|
||||
"schemars",
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue