Fix empty section body when content follows a property drawer
section_body_text() checked for a drawer START (:NAME:) before checking for a drawer END (:END:). But :END: itself matches the generic :NAME: pattern, so the end line re-opened a drawer instead of closing it, leaving in_drawer set and swallowing the body that followed. A section whose only content came after a property drawer therefore rendered an empty /body (and, by extension, empty fuzzy-resolve bodies and GUI sections). Reorder the two checks: when already inside a drawer, handle :END: first and consume the line, before the drawer-start pattern can match it. This also leaves planning-line and leading-blank handling intact. Add regression tests: body after a drawer directly under the headline, and body after a planning line plus drawer. Server tests: 63 passed.
This commit is contained in:
parent
2f040bf810
commit
de43a3721d
|
|
@ -1126,6 +1126,18 @@ fn section_body_text(content: &str, s: &Section) -> String {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Handle drawer end first: when inside a drawer, consume lines until
|
||||
// :END:. This must precede the drawer-start check below, because
|
||||
// :END: also matches the generic :NAME: drawer-start pattern and
|
||||
// would otherwise be misread as opening a new drawer (swallowing the
|
||||
// body that follows).
|
||||
if in_drawer {
|
||||
if trimmed == ":END:" {
|
||||
in_drawer = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle drawer start
|
||||
if trimmed.starts_with(':') && trimmed.ends_with(':') && trimmed.len() > 2 {
|
||||
let drawer_name = &trimmed[1..trimmed.len() - 1];
|
||||
|
|
@ -1135,14 +1147,6 @@ fn section_body_text(content: &str, s: &Section) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// Handle drawer end
|
||||
if in_drawer {
|
||||
if trimmed == ":END:" {
|
||||
in_drawer = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is actual content
|
||||
seen_content = true;
|
||||
result_lines.push(line);
|
||||
|
|
@ -2408,6 +2412,29 @@ mod tests {
|
|||
(dir, state)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn body_after_drawer_is_not_swallowed() {
|
||||
// Regression: :END: matched the generic :NAME: drawer-start pattern,
|
||||
// re-opening a drawer and swallowing the body that followed. A section
|
||||
// whose only content follows a property drawer must still expose it.
|
||||
let (_d, state) = state_with(
|
||||
"#+TITLE: W\n* Task A\n:PROPERTIES:\n:ID: a1\n:END:\nThis is the body of A.\n",
|
||||
);
|
||||
let body = state.section_body("w", "a1").unwrap();
|
||||
assert_eq!(body, "This is the body of A.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn body_after_planning_and_drawer() {
|
||||
// Planning line + property drawer, then body. All three preamble parts
|
||||
// are stripped; only the body remains.
|
||||
let (_d, state) = state_with(
|
||||
"#+TITLE: W\n* TODO Blocked\nDEADLINE: <2025-01-01>\n:PROPERTIES:\n:BLOCKER: approval\n:END:\nWaiting on external input.\n",
|
||||
);
|
||||
let body = state.section_body("w", "blocked").unwrap();
|
||||
assert_eq!(body, "Waiting on external input.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_section() {
|
||||
let (_d, state) = state_with("#+TITLE: W\n* One\n* Two\n* Three\n");
|
||||
|
|
|
|||
Loading…
Reference in New Issue