use std::fmt;
use std::ops::Range;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Mode {
Read,
#[default]
Source,
LivePreview,
}
impl Mode {
pub fn to_data_attr_value(&self) -> &'static str {
match self {
Mode::Read => "read",
Mode::Source => "source",
Mode::LivePreview => "live-preview",
}
}
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_data_attr_value())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Layout {
#[default]
Horizontal,
Vertical,
}
impl Layout {
pub fn as_attr(self) -> &'static str {
match self {
Layout::Horizontal => "horizontal",
Layout::Vertical => "vertical",
}
}
}
pub type Orientation = Layout;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CursorPosition {
pub line: u32,
pub column: u32,
pub offset: usize,
}
impl CursorPosition {
pub fn new(line: u32, column: u32, offset: usize) -> Self {
Self {
line,
column,
offset,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Selection {
pub anchor: usize,
pub head: usize,
}
impl Selection {
pub fn new(anchor: usize, head: usize) -> Self {
Self { anchor, head }
}
pub fn is_collapsed(&self) -> bool {
self.anchor == self.head
}
pub fn len(&self) -> usize {
self.anchor.abs_diff(self.head)
}
pub fn is_empty(&self) -> bool {
self.anchor == self.head
}
pub fn is_forward(&self) -> bool {
self.anchor <= self.head
}
pub fn ordered(&self) -> (usize, usize) {
if self.anchor <= self.head {
(self.anchor, self.head)
} else {
(self.head, self.anchor)
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParseState {
#[default]
Idle,
Parsing,
Done,
Error,
}
impl ParseState {
pub fn to_data_attr_value(&self) -> &'static str {
match self {
ParseState::Idle => "idle",
ParseState::Parsing => "parsing",
ParseState::Done => "done",
ParseState::Error => "error",
}
}
}
impl fmt::Display for ParseState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_data_attr_value())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParseOptions {
pub debounce_ms: u64,
pub tab_size: u8,
pub tables: bool,
pub task_lists: bool,
pub strikethrough: bool,
pub footnotes: bool,
pub front_matter_delimiter: Option<String>,
pub autolink: bool,
}
impl Default for ParseOptions {
fn default() -> Self {
Self {
debounce_ms: 300,
tab_size: 2,
tables: true,
task_lists: true,
strikethrough: true,
footnotes: true,
front_matter_delimiter: Some("---".to_string()),
autolink: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeadingEntry {
pub level: u8,
pub text: String,
pub anchor: String,
pub line: usize,
}
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub enum LivePreviewVariant {
#[default]
SplitPane,
Inline,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BlockEntry {
pub index: usize,
pub raw: String,
pub html: String,
pub start_line: u32,
pub end_line: u32,
pub is_list_item: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeType {
Paragraph,
Heading(u8),
BlockQuote,
CodeBlock(String), List(Option<u64>), Item,
Table,
TableHead,
TableRow,
TableCell,
Rule,
HtmlBlock,
DefinitionList,
DefinitionListTitle,
DefinitionListDefinition,
Superscript,
Subscript,
Text(String),
Code(String),
Html(String),
Emphasis,
Strong,
Strikethrough,
Link { url: String, title: String },
Image { url: String, title: String },
FootnoteReference(String),
SoftBreak,
HardBreak,
TaskListMarker(bool),
Wikilink(String),
Tag(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OwnedAstNode {
pub node_type: NodeType,
pub range: Range<usize>,
pub children: Vec<OwnedAstNode>,
}
pub struct ParsedDoc {
pub element: dioxus::prelude::Element,
pub headings: Vec<HeadingEntry>,
pub front_matter: Option<String>,
pub blocks: Vec<BlockEntry>,
pub ast: Vec<OwnedAstNode>,
}
impl PartialEq for ParsedDoc {
fn eq(&self, _other: &Self) -> bool {
false
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HtmlRenderPolicy {
#[default]
Escape,
Sanitized,
Trusted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VimMode {
#[default]
Insert,
Normal,
Visual,
Command,
}
#[derive(Debug, Clone, PartialEq)]
pub enum VimAction {
PassThrough,
PreventAndEval(String),
ModeChange(VimMode),
ExecuteCommand(String),
}
#[derive(Debug, Clone, Default)]
pub struct VimState {
pub mode: VimMode,
pub command_buffer: String,
}
impl VimState {
pub fn handle_key(&mut self, key: &str, ctrl: bool, shift: bool, editor_id: &str) -> VimAction {
match self.mode {
VimMode::Insert => {
if key == "Escape" {
self.mode = VimMode::Normal;
return VimAction::ModeChange(VimMode::Normal);
}
VimAction::PassThrough
}
VimMode::Normal => match key {
"i" if !ctrl && !shift => {
self.mode = VimMode::Insert;
VimAction::ModeChange(VimMode::Insert)
}
"v" if !ctrl && !shift => {
self.mode = VimMode::Visual;
VimAction::ModeChange(VimMode::Visual)
}
":" => {
self.mode = VimMode::Command;
self.command_buffer.clear();
VimAction::ModeChange(VimMode::Command)
}
"Escape" => VimAction::PassThrough, "h" => VimAction::PreventAndEval(vim_move_js(editor_id, "left")),
"l" => VimAction::PreventAndEval(vim_move_js(editor_id, "right")),
"j" => VimAction::PreventAndEval(vim_move_js(editor_id, "down")),
"k" => VimAction::PreventAndEval(vim_move_js(editor_id, "up")),
_ => VimAction::PassThrough,
},
VimMode::Visual => {
if key == "Escape" {
self.mode = VimMode::Normal;
return VimAction::ModeChange(VimMode::Normal);
}
VimAction::PassThrough
}
VimMode::Command => {
if key == "Escape" {
self.mode = VimMode::Normal;
self.command_buffer.clear();
return VimAction::ModeChange(VimMode::Normal);
}
if key == "Enter" {
let cmd = self.command_buffer.clone();
self.command_buffer.clear();
self.mode = VimMode::Normal;
return VimAction::ExecuteCommand(cmd);
}
if key.len() == 1 {
self.command_buffer.push_str(key);
}
VimAction::PassThrough
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceMapEntry {
pub source_line_start: usize,
pub source_line_end: usize,
pub element_id: String,
}
#[derive(Debug, Clone, Default)]
pub struct SourceMap {
pub entries: Vec<SourceMapEntry>,
}
impl SourceMap {
pub fn find_entry_by_line(&self, line: usize) -> Option<&SourceMapEntry> {
self.entries
.iter()
.find(|e| e.source_line_start <= line && line <= e.source_line_end)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ActiveBlockInputEvent {
pub raw_text: String,
pub visible_text: String,
pub cursor_raw_utf16: usize,
pub cursor_visible_utf16: usize,
pub block_start: usize,
pub block_end: usize,
}
pub(crate) fn vim_move_js(editor_id: &str, direction: &str) -> String {
match direction {
"left" => format!(
"(function(){{ const el = document.getElementById('{editor_id}'); if(!el) return; \
el.selectionStart = el.selectionEnd = Math.max(0, el.selectionStart - 1); }})();"
),
"right" => format!(
"(function(){{ const el = document.getElementById('{editor_id}'); if(!el) return; \
const max = el.value.length; \
el.selectionStart = el.selectionEnd = Math.min(max, el.selectionEnd + 1); }})();"
),
"up" => format!(
"(function(){{ const el = document.getElementById('{editor_id}'); if(!el) return; \
const pos = el.selectionStart; const text = el.value; \
const lineStart = text.lastIndexOf('\\n', pos - 1) + 1; \
const col = pos - lineStart; \
const prevLineEnd = lineStart > 0 ? lineStart - 1 : 0; \
const prevLineStart = text.lastIndexOf('\\n', prevLineEnd - 1) + 1; \
const newPos = Math.min(prevLineStart + col, prevLineEnd); \
el.selectionStart = el.selectionEnd = newPos; }})();"
),
"down" => format!(
"(function(){{ const el = document.getElementById('{editor_id}'); if(!el) return; \
const pos = el.selectionStart; const text = el.value; \
const lineStart = text.lastIndexOf('\\n', pos - 1) + 1; \
const col = pos - lineStart; \
const lineEnd = text.indexOf('\\n', pos); \
if(lineEnd === -1) return; \
const nextLineStart = lineEnd + 1; \
const nextLineEnd = text.indexOf('\\n', nextLineStart); \
const nextLineLen = (nextLineEnd === -1 ? text.length : nextLineEnd) - nextLineStart; \
el.selectionStart = el.selectionEnd = nextLineStart + Math.min(col, nextLineLen); }})();"
),
_ => String::new(),
}
}