Skip to main content

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}