documents: format refuses ends in marks, clears more, reads long texts apart

Unwrap and merge check the original ends against marks and link
addresses. Blocks take text up to their end spaces; a cell between its
pipes. Clear also takes inline math's dollars, footnote brackets and
escape backslashes. Underline finds its <u> pair past other tags. Blank
lines split on CR too; a selection across blocks says Across. Past
128 KiB the toggle runs off the UI thread and lands only if text and
selection are unchanged; past the largest read it refuses (TooLong).
Clear and the document command move into format/.

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 12:16:09 -04:00
1 parent 68d33b4c54
commit d238e3b4f0
6 files changed
+395 -166

No files matched your search

+85 -3
View File
@@ -1,11 +1,11 @@
//! Inline formats toggle as Typora's (R4.6).
use gpui::{Modifiers, TestAppContext};
use gpui::{Entity, Modifiers, TestAppContext, VisualTestContext};
use super::{open, opened, press, text};
use crate::documents::{
FormatRefusal, InlineFormat, MarkdownMode,
document::{format::toggle, visual},
FormatRefusal, InlineFormat, LARGEST_READ, MarkdownDocument, MarkdownMode,
document::{AT_ONCE, format::toggle, visual},
};
/// `marked` toggled: `‸` is the caret, `⟨…⟩` the selection.
@@ -178,3 +178,85 @@ fn preview_refuses_a_format(cx: &mut TestAppContext) {
assert_eq!(refused, Err(FormatRefusal::Preview));
assert_eq!(text(&document, cx), "alpha\n");
}
#[test]
fn an_end_in_marks_or_an_address_refuses() {
use InlineFormat::*;
let across = Err(FormatRefusal::Across);
assert_eq!(toggled("a *‸*bold** z", Strong), across);
assert_eq!(toggled("[a](ht‸tps://example.com)", Link), across);
assert_eq!(toggled("a *⟨*b** c⟩", Strong), across);
assert_eq!(toggled("*a⟨b*⟩", Strong), across);
assert_eq!(toggled("⟨alpha\n\nbeta⟩", Strong), across);
}
#[test]
fn end_spaces_cells_and_cr_lines_take_a_pair() {
use InlineFormat::*;
assert_eq!(ok("alpha ‸", Strong), "alpha **‸**");
assert_eq!(ok("| h |\n| - |\n| ‸ |", Strong), "| h |\n| - |\n| **‸** |");
assert_eq!(ok("a\r‸\rb", Strong), "a\r**‸**\rb");
}
#[test]
fn clear_takes_math_footnote_and_escape_marks() {
use InlineFormat::Clear;
assert_eq!(ok("**a** $x‸+y$", Clear), "a x‸+y");
assert_eq!(ok("a‸ [^n]\n\n[^n]: note", Clear), "a‸ n\n\n[^n]: note");
assert_eq!(ok(r"a‸ \*b\*", Clear), "a‸ *b*");
}
#[test]
fn underline_comes_off_past_other_tags() {
use InlineFormat::Underline;
assert_eq!(ok("<u>a <br>b‸c</u>", Underline), "a <br>b‸c");
assert_eq!(ok("<u>a <em>b‸c</em> d</u>", Underline), "a <em>b‸c</em> d");
}
/// A source document of one long word, caret at 1.
fn long(cx: &mut TestAppContext, len: usize) -> (Entity<MarkdownDocument>, &mut VisualTestContext) {
let (document, cx) = open(&"x".repeat(len), MarkdownMode::Source, cx);
document.update(cx, |document, cx| {
document
.editor
.update(cx, |editor, cx| editor.select(std::iter::once(1..1), cx));
});
(document, cx)
}
#[gpui::test]
fn a_long_text_formats_once_read_apart(cx: &mut TestAppContext) {
let (document, cx) = long(cx, AT_ONCE + 1);
document.update(cx, |document, cx| {
document.format(InlineFormat::Strong, cx).expect("taken")
});
assert!(
!text(&document, cx).starts_with("**"),
"not on the UI thread"
);
cx.run_until_parked();
assert!(text(&document, cx).starts_with("**x"));
}
#[gpui::test]
fn a_long_format_lapses_when_the_text_moves(cx: &mut TestAppContext) {
let (document, cx) = long(cx, AT_ONCE + 1);
document.update(cx, |document, cx| {
document.format(InlineFormat::Strong, cx).expect("taken");
document
.editor
.update(cx, |editor, cx| editor.edit([(0..0, "y".to_string())], cx));
});
cx.run_until_parked();
assert!(
text(&document, cx).starts_with("yx"),
"the typing stays, unformatted"
);
}
#[gpui::test]
fn a_text_past_the_largest_read_refuses(cx: &mut TestAppContext) {
let (document, cx) = long(cx, LARGEST_READ + 1);
let refused = document.update(cx, |document, cx| document.format(InlineFormat::Strong, cx));
assert_eq!(refused, Err(FormatRefusal::TooLong));
}