use std::ops::Range;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Doc {
pub blocks: Vec<Block>,
}
impl Doc {
pub(crate) fn renumber(&mut self) {
let mut expected: Vec<Option<u64>> = Vec::new();
for block in &mut self.blocks {
let indent = block.indent as usize;
expected.truncate(indent + 1);
expected.resize(indent + 1, None);
if let BlockKind::Ordered { number, .. } = &mut block.kind {
if let Some(next) = expected[indent] {
*number = next;
}
expected[indent] = Some(number.saturating_add(1));
} else {
expected[indent] = None;
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Block {
pub kind: BlockKind,
pub indent: u8,
}
impl From<BlockKind> for Block {
fn from(kind: BlockKind) -> Self {
Self::new(kind)
}
}
impl Block {
pub fn new(kind: BlockKind) -> Self {
Self { kind, indent: 0 }
}
pub fn at(kind: BlockKind, indent: u8) -> Self {
Self { kind, indent }
}
pub fn text_at(&self, part: Part) -> Option<&Text> {
match (&self.kind, part) {
(
BlockKind::Paragraph(text)
| BlockKind::Heading { text, .. }
| BlockKind::Bullet(text)
| BlockKind::Ordered { text, .. }
| BlockKind::Task { text, .. }
| BlockKind::Quote { text, .. },
Part::Body,
) => Some(text),
(BlockKind::Code { code, .. }, Part::Code) => Some(code),
(BlockKind::Image { alt, .. }, Part::Caption) => Some(alt),
(BlockKind::Table { header, .. }, Part::Cell { row: 0, column }) => header.get(column),
(BlockKind::Table { rows, .. }, Part::Cell { row, column }) => {
rows.get(row - 1)?.get(column)
}
_ => None,
}
}
pub fn text_at_mut(&mut self, part: Part) -> Option<&mut Text> {
match (&mut self.kind, part) {
(
BlockKind::Paragraph(text)
| BlockKind::Heading { text, .. }
| BlockKind::Bullet(text)
| BlockKind::Ordered { text, .. }
| BlockKind::Task { text, .. }
| BlockKind::Quote { text, .. },
Part::Body,
) => Some(text),
(BlockKind::Code { code, .. }, Part::Code) => Some(code),
(BlockKind::Image { alt, .. }, Part::Caption) => Some(alt),
(BlockKind::Table { header, .. }, Part::Cell { row: 0, column }) => {
header.get_mut(column)
}
(BlockKind::Table { rows, .. }, Part::Cell { row, column }) => {
rows.get_mut(row - 1)?.get_mut(column)
}
_ => None,
}
}
pub fn parts(&self) -> Vec<Part> {
match &self.kind {
BlockKind::Paragraph(_)
| BlockKind::Heading { .. }
| BlockKind::Bullet(_)
| BlockKind::Ordered { .. }
| BlockKind::Task { .. }
| BlockKind::Quote { .. } => vec![Part::Body],
BlockKind::Code { .. } => vec![Part::Code],
BlockKind::Image { .. } => vec![Part::Caption],
BlockKind::Table { header, rows, .. } => {
let mut parts = Vec::new();
if !header.is_empty() {
parts.extend((0..header.len()).map(|column| Part::Cell { row: 0, column }));
}
for (ix, row) in rows.iter().enumerate() {
parts.extend((0..row.len()).map(|column| Part::Cell {
row: ix + 1,
column,
}));
}
parts
}
BlockKind::Bookmark { .. } | BlockKind::Rule => Vec::new(),
}
}
pub fn opaque(&self) -> bool {
matches!(
self.kind,
BlockKind::Image { .. } | BlockKind::Bookmark { .. } | BlockKind::Rule
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Part {
#[default]
Body,
Code,
Caption,
Cell {
row: usize,
column: usize,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BlockKind {
Paragraph(Text),
Heading {
level: u8,
text: Text,
},
Bullet(Text),
Ordered {
number: u64,
text: Text,
},
Task {
checked: bool,
text: Text,
},
Quote {
kind: Option<QuoteKind>,
text: Text,
},
Code {
language: Option<String>,
code: Text,
},
Image {
url: String,
alt: Text,
width: Option<u32>,
},
Bookmark {
url: String,
form: Form,
},
Table {
align: Vec<Align>,
header: Vec<Text>,
rows: Vec<Vec<Text>>,
},
Rule,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuoteKind {
Note,
Tip,
Important,
Warning,
Caution,
}
impl QuoteKind {
pub fn marker(self) -> &'static str {
match self {
Self::Note => "[!NOTE]",
Self::Tip => "[!TIP]",
Self::Important => "[!IMPORTANT]",
Self::Warning => "[!WARNING]",
Self::Caution => "[!CAUTION]",
}
}
pub fn label(self) -> &'static str {
match self {
Self::Note => "Note",
Self::Tip => "Tip",
Self::Important => "Important",
Self::Warning => "Warning",
Self::Caution => "Caution",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Align {
#[default]
Left,
Center,
Right,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Text {
pub text: String,
pub marks: Vec<MarkSpan>,
}
impl From<&str> for Text {
fn from(text: &str) -> Self {
Self::plain(text)
}
}
impl From<String> for Text {
fn from(text: String) -> Self {
Self::plain(text)
}
}
impl Text {
pub fn plain(text: impl Into<String>) -> Self {
Self {
text: text.into(),
marks: Vec::new(),
}
}
pub fn link(url: &str) -> Self {
Self {
text: url.to_string(),
marks: vec![MarkSpan {
range: 0..url.len(),
mark: Mark::Link(url.to_string()),
}],
}
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
pub(crate) fn alone(&self, ix: usize) -> bool {
let span = &self.marks[ix].range;
self.marks.iter().enumerate().all(|(other, mark)| {
other == ix || mark.range.end <= span.start || mark.range.start >= span.end
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MarkSpan {
pub range: Range<usize>,
pub mark: Mark,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Form {
Auto,
Chip,
Embed,
}
impl Form {
pub(crate) fn title(self) -> Option<&'static str> {
match self {
Self::Auto => None,
Self::Chip => Some("chip"),
Self::Embed => Some("embed"),
}
}
pub(crate) fn from_title(title: &str) -> Option<Self> {
match title {
"chip" => Some(Self::Chip),
"embed" => Some(Self::Embed),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Mark {
Bold,
Italic,
Strike,
Code,
Link(String),
Mention {
url: String,
form: Form,
},
Custom(String),
Image(String),
}