documents: a dropped block hands its focus to the document
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:
1 parent
24ffc45c6b
commit
6c2d28c56e
7 files changed
+42
-4
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user