documents: a dropped block hands its focus to the document
CI / platforms (windows-latest) (push) Failing after 8s
CI / site (push) Successful in 21s
CI / crate (push) Failing after 12m5s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s

An edit past an open block wrote it back and dropped its input while
it held focus, so focus fell to the window root and Undo reached
nothing. The next render passes such focus to the document.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-14 17:39:24 -04:00
1 parent 24ffc45c6b
commit 6c2d28c56e
7 files changed
+42 -4

No files matched your search

+12
View File
@@ -141,6 +141,17 @@ impl MarkdownDocument {
cx.notify();
}
/// Takes the focus a dropped block still holds.
pub(super) fn take_orphan(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(orphan) = self.orphan.take() else {
return;
};
if self.open.is_none() && orphan.is_focused(window) {
log::info!("markdown document: a dropped block's focus passes to the document");
window.focus(&self.focus_handle(cx), cx);
}
}
/// Keeps the block open while an overlay holds focus.
fn lend(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let (Some(open), Some(holder)) = (self.open.as_mut(), window.focused(cx)) else {
@@ -168,6 +179,7 @@ impl MarkdownDocument {
/// Writes the open block as one step; the edit made.
pub(super) fn commit(&mut self, cx: &mut Context<Self>) -> Option<(Range<usize>, usize)> {
let open = self.open.take()?;
self.orphan = Some(open.input.focus_handle(cx));
cx.notify();
let typed = open.input.read(cx).text().to_string();
let caret = open.input.read(cx).cursor();
+5 -1
View File
@@ -97,6 +97,8 @@ pub struct MarkdownDocument {
editing: bool,
list: ListState,
open: Option<Open>,
/// A dropped block's focus, for the document to take.
orphan: Option<FocusHandle>,
/// A top line asked for, until the view holds it.
top: Option<Placement>,
asks: u64,
@@ -169,6 +171,7 @@ impl MarkdownDocument {
editing: false,
list: ListState::new(1, ListAlignment::Top, overdraw),
open: None,
orphan: None,
top: None,
asks: 0,
writes: 0,
@@ -344,7 +347,8 @@ impl MarkdownDocument {
/// Closes the open block without writing it.
fn drop_open(&mut self, cx: &mut Context<Self>) {
if self.open.take().is_some() {
if let Some(open) = self.open.take() {
self.orphan = Some(open.input.focus_handle(cx));
log::info!("markdown document: the open block closed unwritten");
cx.notify();
}
+1 -1
View File
@@ -164,7 +164,7 @@ pub(super) fn edit_table(text: &str, at: usize, edit: TableEdit) -> Result<Chang
Ok((vec![(range, out)], caret..caret))
}
/// The table at the caret gone, as the block command removes it.
/// Removes the table at the caret, as Delete does.
pub(super) fn delete(text: &str, at: usize) -> Result<Change, EditRefusal> {
let root = file_tree(text);
let mut nodes = Vec::new();
+1 -1
View File
@@ -12,7 +12,7 @@ use crate::{
actions!(lend_tests, [Bold, Away]);
/// A menu bar over a document, as an app lays them out.
/// A menu bar over a document.
struct Host {
document: Entity<MarkdownDocument>,
away: FocusHandle,
+21
View File
@@ -411,3 +411,24 @@ fn delete_table_takes_the_table_or_refuses_outside(cx: &mut TestAppContext) {
assert_eq!(*heard.borrow(), [outside]);
assert_eq!(text(&document, cx), source);
}
#[gpui::test]
fn undo_follows_a_table_edit_that_closes_the_block(cx: &mut TestAppContext) {
let source = "before\n\n| A |\n| - |\n| 1 |\n\nafter\n";
let (document, cx) = open(source, MarkdownMode::Visual, cx);
press(1, Modifiers::none(), cx);
document.update(cx, |document, cx| {
document
.edit_table(crate::documents::TableEdit::Delete, cx)
.expect("taken")
});
cx.run_until_parked();
assert_eq!(text(&document, cx), "before\n\nafter\n");
let held = cx.update(|window, cx| {
let handle = gpui::Focusable::focus_handle(document.read(cx), cx);
handle.contains_focused(window, cx)
});
assert!(held, "focus stays in the document");
cx.simulate_keystrokes("secondary-z");
assert_eq!(text(&document, cx), source);
}
+1
View File
@@ -195,6 +195,7 @@ impl MarkdownDocument {
impl Render for MarkdownDocument {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
self.take_orphan(window, cx);
// A top line asked before its text was read.
self.place_top(window, cx);
self.show_waiting(window, cx);