use mq_markdown::{Markdown, Node};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cursor {
pub line: usize,
pub column: usize,
pub desired_column: usize,
}
impl Cursor {
pub fn new() -> Self {
Self {
line: 0,
column: 0,
desired_column: 0,
}
}
pub fn with_position(line: usize, column: usize) -> Self {
Self {
line,
column,
desired_column: column,
}
}
pub fn update_desired_column(&mut self) {
self.desired_column = self.column;
}
}
impl Default for Cursor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorMovement {
Up,
Down,
Left,
Right,
StartOfLine,
EndOfLine,
PageUp,
PageDown,
StartOfDocument,
EndOfDocument,
}
#[derive(Debug, Clone)]
pub struct LineMap {
entries: Vec<LineEntry>,
}
#[derive(Debug, Clone)]
pub struct LineEntry {
pub node_index: usize,
pub node_line_offset: usize,
pub visual_line: usize,
pub char_offset: usize,
}
impl LineMap {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn from_markdown(markdown: &Markdown) -> Self {
let mut map = Self::new();
let mut current_line = 0;
for (node_idx, node) in markdown.nodes.iter().enumerate() {
Self::process_node(&mut map, node, node_idx, &mut current_line);
}
map
}
fn process_node(map: &mut LineMap, node: &Node, node_idx: usize, current_line: &mut usize) {
if let Some(pos) = node.position() {
let start_line = pos.start.line.saturating_sub(1); let end_line = pos.end.line.saturating_sub(1);
for line_offset in 0..=(end_line - start_line) {
map.entries.push(LineEntry {
node_index: node_idx,
node_line_offset: line_offset,
visual_line: *current_line,
char_offset: 0,
});
*current_line += 1;
}
} else {
map.entries.push(LineEntry {
node_index: node_idx,
node_line_offset: 0,
visual_line: *current_line,
char_offset: 0,
});
*current_line += 1;
}
}
pub fn get_entry(&self, line: usize) -> Option<&LineEntry> {
self.entries.get(line)
}
pub fn get_node_at_line<'a>(&self, markdown: &'a Markdown, line: usize) -> Option<&'a Node> {
let entry = self.get_entry(line)?;
markdown.nodes.get(entry.node_index)
}
pub fn line_count(&self) -> usize {
self.entries.len()
}
pub fn invalidate_from(&mut self, from_line: usize) {
self.entries.truncate(from_line);
}
pub fn rebuild_from(&mut self, markdown: &Markdown, from_node_idx: usize) {
let mut current_line = if let Some(entry) = self.entries.last() {
entry.visual_line + 1
} else {
0
};
for (idx, node) in markdown.nodes.iter().enumerate().skip(from_node_idx) {
Self::process_node(self, node, idx, &mut current_line);
}
}
}
impl Default for LineMap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cursor_creation() {
let cursor = Cursor::new();
assert_eq!(cursor.line, 0);
assert_eq!(cursor.column, 0);
assert_eq!(cursor.desired_column, 0);
}
#[test]
fn test_cursor_with_position() {
let cursor = Cursor::with_position(5, 10);
assert_eq!(cursor.line, 5);
assert_eq!(cursor.column, 10);
assert_eq!(cursor.desired_column, 10);
}
#[test]
fn test_linemap_creation() {
let linemap = LineMap::new();
assert_eq!(linemap.line_count(), 0);
}
}