use crate::enums::{BlockType, Code, EmitFlag, ListType};
use regex::Regex;
use serde::{Deserialize, Serialize};
pub const BGRESET: &str = "\x1b[49m";
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct InlineState {
pub inline_code: bool,
pub in_bold: bool,
pub in_italic: bool,
pub in_underline: bool,
pub in_strikeout: bool,
}
#[derive(Debug, Clone)]
pub struct ParseState {
pub buffer: Vec<u8>,
pub current_line: String,
pub first_line: bool,
pub last_line_empty: bool,
pub is_pty: bool,
pub is_exec: bool,
pub maybe_prompt: bool,
pub prompt_regex: Option<Regex>,
pub emit_flag: Option<EmitFlag>,
pub scrape: Option<String>,
pub scrape_ix: usize,
pub terminal: Option<String>,
pub width_arg: Option<usize>,
pub width_full: Option<usize>,
pub width_wrap: bool,
pub first_indent: Option<usize>,
pub has_newline: bool,
pub bg: String,
pub code_buffer: String,
pub code_buffer_raw: String,
pub code_gen: usize,
pub code_language: Option<String>,
pub code_first_line: bool,
pub code_indent: usize,
pub code_line: String,
pub ordered_list_numbers: Vec<usize>,
pub list_item_stack: Vec<(usize, ListType)>,
pub list_indent_text: usize,
pub in_list: bool,
pub in_code: Option<Code>,
pub inline_code: bool,
pub in_bold: bool,
pub in_italic: bool,
pub in_table: Option<Code>,
pub in_underline: bool,
pub in_strikeout: bool,
pub block_depth: usize,
pub block_type: Option<BlockType>,
pub exec_sub: Option<String>,
pub exec_master: Option<i32>,
pub exec_slave: Option<i32>,
pub exec_kb: usize,
pub exit: i32,
pub where_from: Option<String>,
pub links: bool,
pub images: bool,
pub code_spaces: bool,
pub clipboard: bool,
pub logging: bool,
pub timeout: f64,
pub savebrace: bool,
}
impl Default for ParseState {
fn default() -> Self {
Self::new()
}
}
impl ParseState {
pub fn new() -> Self {
Self {
buffer: Vec::new(),
current_line: String::new(),
first_line: true,
last_line_empty: true,
is_pty: false,
is_exec: false,
maybe_prompt: false,
prompt_regex: None,
emit_flag: None,
scrape: None,
scrape_ix: 0,
terminal: None,
width_arg: None,
width_full: None,
width_wrap: false,
first_indent: None,
has_newline: false,
bg: BGRESET.to_string(),
code_buffer: String::new(),
code_buffer_raw: String::new(),
code_gen: 0,
code_language: None,
code_first_line: false,
code_indent: 0,
code_line: String::new(),
ordered_list_numbers: Vec::new(),
list_item_stack: Vec::new(),
list_indent_text: 0,
in_list: false,
in_code: None,
inline_code: false,
in_bold: false,
in_italic: false,
in_table: None,
in_underline: false,
in_strikeout: false,
block_depth: 0,
block_type: None,
exec_sub: None,
exec_master: None,
exec_slave: None,
exec_kb: 0,
exit: 0,
where_from: None,
links: true,
images: true,
code_spaces: false,
clipboard: true,
logging: false,
timeout: 0.1,
savebrace: true,
}
}
pub fn current(&self) -> InlineState {
InlineState {
inline_code: self.inline_code,
in_bold: self.in_bold,
in_italic: self.in_italic,
in_underline: self.in_underline,
in_strikeout: self.in_strikeout,
}
}
pub fn reset_inline(&mut self) {
self.inline_code = false;
self.in_bold = false;
self.in_italic = false;
self.in_underline = false;
self.in_strikeout = false;
}
pub fn set_width(&mut self, width: usize) {
self.width_full = Some(width);
}
pub fn full_width(&self, offset: usize) -> usize {
let base = self.width_full.unwrap_or(80);
base.saturating_sub(offset)
}
pub fn current_width(&self, listwidth: bool) -> usize {
let base = self.width_full.unwrap_or(80);
let block_offset = self.block_depth * 2;
let list_offset = if listwidth {
self.list_indent_text
} else {
0
};
base.saturating_sub(block_offset + list_offset)
}
pub fn space_left(&self, listwidth: bool) -> String {
let mut result = String::new();
for _ in 0..self.block_depth {
result.push_str("│ ");
}
if listwidth && self.list_indent_text > 0 {
result.push_str(&" ".repeat(self.list_indent_text));
}
result
}
pub fn is_in_code(&self) -> bool {
self.in_code.is_some()
}
pub fn is_in_table(&self) -> bool {
self.in_table.is_some()
}
pub fn has_inline_formatting(&self) -> bool {
self.inline_code || self.in_bold || self.in_italic || self.in_underline || self.in_strikeout
}
pub fn push_list(&mut self, indent: usize, list_type: ListType) {
self.list_item_stack.push((indent, list_type));
if list_type == ListType::Ordered {
self.ordered_list_numbers.push(1);
}
self.in_list = true;
}
pub fn pop_list(&mut self) -> Option<(usize, ListType)> {
let result = self.list_item_stack.pop();
if let Some((_, ListType::Ordered)) = result {
self.ordered_list_numbers.pop();
}
self.in_list = !self.list_item_stack.is_empty();
result
}
pub fn list_depth(&self) -> usize {
self.list_item_stack.len()
}
pub fn next_list_number(&mut self) -> Option<usize> {
self.ordered_list_numbers.last_mut().map(|n| {
let current = *n;
*n += 1;
current
})
}
pub fn enter_code_block(&mut self, code_type: Code, language: Option<String>) {
self.in_code = Some(code_type);
self.code_language = language;
self.code_first_line = true;
self.code_buffer.clear();
self.code_buffer_raw.clear();
self.code_gen += 1;
}
pub fn exit_code_block(&mut self) {
self.in_code = None;
self.code_language = None;
self.code_first_line = false;
}
pub fn enter_block(&mut self, block_type: BlockType) {
self.block_depth += 1;
self.block_type = Some(block_type);
}
pub fn exit_block(&mut self) {
if self.block_depth > 0 {
self.block_depth -= 1;
}
if self.block_depth == 0 {
self.block_type = None;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_state() {
let state = ParseState::new();
assert!(state.first_line);
assert!(state.last_line_empty);
assert!(!state.in_bold);
assert!(state.in_code.is_none());
assert_eq!(state.block_depth, 0);
}
#[test]
fn test_current_inline_state() {
let mut state = ParseState::new();
state.in_bold = true;
state.in_italic = true;
let inline = state.current();
assert!(inline.in_bold);
assert!(inline.in_italic);
assert!(!inline.inline_code);
}
#[test]
fn test_reset_inline() {
let mut state = ParseState::new();
state.in_bold = true;
state.in_italic = true;
state.in_underline = true;
state.reset_inline();
assert!(!state.in_bold);
assert!(!state.in_italic);
assert!(!state.in_underline);
}
#[test]
fn test_full_width() {
let mut state = ParseState::new();
state.set_width(100);
assert_eq!(state.full_width(0), 100);
assert_eq!(state.full_width(20), 80);
assert_eq!(state.full_width(150), 0); }
#[test]
fn test_current_width_with_blocks() {
let mut state = ParseState::new();
state.set_width(80);
assert_eq!(state.current_width(false), 80);
state.block_depth = 2;
assert_eq!(state.current_width(false), 76);
state.list_indent_text = 4;
assert_eq!(state.current_width(true), 72); assert_eq!(state.current_width(false), 76); }
#[test]
fn test_space_left() {
let mut state = ParseState::new();
assert_eq!(state.space_left(false), "");
state.block_depth = 2;
assert_eq!(state.space_left(false), "│ │ ");
state.list_indent_text = 3;
assert_eq!(state.space_left(true), "│ │ ");
}
#[test]
fn test_list_operations() {
let mut state = ParseState::new();
state.push_list(0, ListType::Ordered);
assert!(state.in_list);
assert_eq!(state.list_depth(), 1);
assert_eq!(state.next_list_number(), Some(1));
assert_eq!(state.next_list_number(), Some(2));
state.push_list(2, ListType::Bullet);
assert_eq!(state.list_depth(), 2);
state.pop_list();
assert_eq!(state.list_depth(), 1);
assert!(state.in_list);
state.pop_list();
assert_eq!(state.list_depth(), 0);
assert!(!state.in_list);
}
#[test]
fn test_code_block_operations() {
let mut state = ParseState::new();
assert!(!state.is_in_code());
state.enter_code_block(Code::Backtick, Some("rust".to_string()));
assert!(state.is_in_code());
assert_eq!(state.code_language, Some("rust".to_string()));
assert!(state.code_first_line);
assert_eq!(state.code_gen, 1);
state.exit_code_block();
assert!(!state.is_in_code());
assert!(state.code_language.is_none());
}
#[test]
fn test_block_operations() {
let mut state = ParseState::new();
state.enter_block(BlockType::Quote);
assert_eq!(state.block_depth, 1);
assert_eq!(state.block_type, Some(BlockType::Quote));
state.enter_block(BlockType::Quote);
assert_eq!(state.block_depth, 2);
state.exit_block();
assert_eq!(state.block_depth, 1);
state.exit_block();
assert_eq!(state.block_depth, 0);
assert!(state.block_type.is_none());
}
}