#ifndef HUSH_DOCUMENT_H
#define HUSH_DOCUMENT_H
#include "strbuf.h"
typedef enum {
BLOCK_PARAGRAPH,
BLOCK_H1,
BLOCK_H2,
BLOCK_H3,
BLOCK_H4,
BLOCK_H5,
BLOCK_H6,
BLOCK_BULLET,
BLOCK_NUMBERED,
BLOCK_QUOTE,
BLOCK_CODE,
BLOCK_HR, /* horizontal rule; text/lang are always empty, never edited */
BLOCK_TASK_UNCHECKED, /* "- [ ] text" */
BLOCK_TASK_CHECKED, /* "- [x] text" */
BLOCK_IMAGE, /* ""; text holds the url, alt holds the alt text */
BLOCK_TABLE_HEADER_CELL, /* one cell of a table's header row; text holds the cell's content */
BLOCK_TABLE_CELL, /* one cell of a table's body row; text holds the cell's content */
} BlockType;
typedef struct {
BlockType type;
StrBuf text; /* raw text, without block-level prefix. Code blocks may embed '\n'. */
StrBuf lang; /* BLOCK_CODE only: the fenced code's info-string (e.g. "c"), or empty. */
StrBuf alt; /* BLOCK_IMAGE only: the image's alt text, or empty. */
/* BLOCK_TABLE_HEADER_CELL/BLOCK_TABLE_CELL only. A table is a contiguous run of these blocks
(like a bulleted list is a run of BLOCK_BULLET blocks) -- these fields let a cell know its
position without needing a separate table-container block or lookahead. tableCol resetting
to 0 marks a new row; tableCol == tableCols-1 marks the last cell of a row. */
int tableCol; /* 0-indexed column within this cell's row */
int tableCols; /* total columns in this cell's table */
char tableAlign; /* 'l' (default), 'c', or 'r', from the delimiter row */
} Block;
typedef struct {
Block *blocks;
int count;
int cap;
} Document;
/* True for block types that "continue" themselves on Enter (list-like blocks). */
static inline int block_type_is_continuable(BlockType t) {
return t == BLOCK_BULLET || t == BLOCK_NUMBERED || t == BLOCK_QUOTE
|| t == BLOCK_TASK_UNCHECKED || t == BLOCK_TASK_CHECKED;
}
/* True for a table cell (header or body) -- Enter/Backspace/Delete/vertical-nav/Turn-Into all
need to treat these specially rather than falling into their generic per-block-type paths. */
static inline int block_type_is_table_cell(BlockType t) {
return t == BLOCK_TABLE_HEADER_CELL || t == BLOCK_TABLE_CELL;
}
void document_init(Document *doc);
void document_free(Document *doc);
/* Deep-copies `src` into `dst`. `dst` must not already hold live blocks (it is treated as
uninitialized, not freed first) — used to snapshot state for undo/redo. */
void document_clone(Document *dst, const Document *src);
/* Inserts a new block at `index`, shifting later blocks right. Text is copied. */
void document_insert_block(Document *doc, int index, BlockType type, const char *text, int len);
void document_remove_block(Document *doc, int index);
static inline Block *document_block(Document *doc, int index) {
return &doc->blocks[index];
}
#endif