bekoedit_markdown/block.rs
1//! Block-level model produced by the Markdown indexer (RFC-013).
2
3use serde::{Deserialize, Serialize};
4
5use crate::fingerprint::BlockId;
6use crate::range::ByteRange;
7use crate::trivia::SourceTrivia;
8
9/// Top-level block categories recognized by the indexer.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum BlockKind {
12 Heading,
13 Paragraph,
14 BulletList,
15 OrderedList,
16 TaskList,
17 Blockquote,
18 FencedCode,
19 HorizontalRule,
20 HtmlBlock,
21 FrontMatter,
22 /// A simple NxM table where all cells are plain text (RFC-027).
23 SimpleTable,
24 /// A table with complex cells (merged, nested formatting, etc.).
25 ComplexTable,
26 /// Anything the indexer does not positively recognize as safe.
27 Unknown,
28}
29
30/// Whether (and how) Form Mode may edit a block.
31///
32/// Per the safe-fallback invariant, the indexer prefers false negatives:
33/// when uncertain, a block is downgraded to `RawIslandOnly`.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35pub enum EditablePolicy {
36 /// Structured semantic edits are allowed (e.g. paragraph text, heading level).
37 FormEditable,
38 /// Only raw-text island editing is allowed; the source is shown verbatim.
39 RawIslandOnly,
40 /// Visual block with delete action only (e.g. horizontal rule).
41 DeleteOnly,
42}
43
44/// One list item inside a (task/bullet/ordered) list block.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct ListItemNode {
47 /// Zero-based ordinal within the parent block, revision-scoped (RFC-018).
48 pub ordinal: u32,
49 /// Full source range of the item line(s), excluding the trailing newline.
50 pub source_range: ByteRange,
51 /// Range of the editable item text (after marker and optional checkbox).
52 pub content_range: ByteRange,
53 /// Checkbox state for task items; `None` for plain items.
54 pub task_checked: Option<bool>,
55}
56
57/// Heading entry for the outline projection.
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct HeadingNode {
60 pub level: u8,
61 pub text: String,
62 pub source_range: ByteRange,
63}
64
65/// A parsed top-level block with Rust-owned source ranges.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct BlockNode {
68 pub block_id: BlockId,
69 pub kind: BlockKind,
70 /// Full source range of the block, excluding surrounding blank lines.
71 pub source_range: ByteRange,
72 /// Range of the directly editable content, when the kind supports it:
73 /// heading text after the marker, paragraph text, fenced code body, …
74 pub content_range: Option<ByteRange>,
75 pub trivia: SourceTrivia,
76 pub editable_policy: EditablePolicy,
77 /// Heading level for `Heading` blocks.
78 pub heading_level: Option<u8>,
79 /// Items for list blocks.
80 pub items: Vec<ListItemNode>,
81 /// Code fence info string (language) for `FencedCode` blocks.
82 pub code_language: Option<String>,
83}
84
85impl BlockNode {
86 pub fn is_form_editable(&self) -> bool {
87 matches!(
88 self.editable_policy,
89 EditablePolicy::FormEditable | EditablePolicy::DeleteOnly
90 )
91 }
92}