mq_edit/document/
cursor.rs1use mq_markdown::{Markdown, Node};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Cursor {
6 pub line: usize,
7 pub column: usize,
8 pub desired_column: usize,
10}
11
12impl Cursor {
13 pub fn new() -> Self {
14 Self {
15 line: 0,
16 column: 0,
17 desired_column: 0,
18 }
19 }
20
21 pub fn with_position(line: usize, column: usize) -> Self {
22 Self {
23 line,
24 column,
25 desired_column: column,
26 }
27 }
28
29 pub fn update_desired_column(&mut self) {
31 self.desired_column = self.column;
32 }
33}
34
35impl Default for Cursor {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum CursorMovement {
44 Up,
45 Down,
46 Left,
47 Right,
48 StartOfLine,
49 EndOfLine,
50 PageUp,
51 PageDown,
52 StartOfDocument,
53 EndOfDocument,
54}
55
56#[derive(Debug, Clone)]
58pub struct LineMap {
59 entries: Vec<LineEntry>,
60}
61
62#[derive(Debug, Clone)]
63pub struct LineEntry {
64 pub node_index: usize,
66 pub node_line_offset: usize,
68 pub visual_line: usize,
70 pub char_offset: usize,
72}
73
74impl LineMap {
75 pub fn new() -> Self {
76 Self {
77 entries: Vec::new(),
78 }
79 }
80
81 pub fn from_markdown(markdown: &Markdown) -> Self {
83 let mut map = Self::new();
84 let mut current_line = 0;
85
86 for (node_idx, node) in markdown.nodes.iter().enumerate() {
87 Self::process_node(&mut map, node, node_idx, &mut current_line);
88 }
89
90 map
91 }
92
93 fn process_node(map: &mut LineMap, node: &Node, node_idx: usize, current_line: &mut usize) {
94 if let Some(pos) = node.position() {
95 let start_line = pos.start.line.saturating_sub(1); let end_line = pos.end.line.saturating_sub(1);
97
98 for line_offset in 0..=(end_line - start_line) {
99 map.entries.push(LineEntry {
100 node_index: node_idx,
101 node_line_offset: line_offset,
102 visual_line: *current_line,
103 char_offset: 0,
104 });
105 *current_line += 1;
106 }
107 } else {
108 map.entries.push(LineEntry {
110 node_index: node_idx,
111 node_line_offset: 0,
112 visual_line: *current_line,
113 char_offset: 0,
114 });
115 *current_line += 1;
116 }
117 }
118
119 pub fn get_entry(&self, line: usize) -> Option<&LineEntry> {
121 self.entries.get(line)
122 }
123
124 pub fn get_node_at_line<'a>(&self, markdown: &'a Markdown, line: usize) -> Option<&'a Node> {
126 let entry = self.get_entry(line)?;
127 markdown.nodes.get(entry.node_index)
128 }
129
130 pub fn line_count(&self) -> usize {
132 self.entries.len()
133 }
134
135 pub fn invalidate_from(&mut self, from_line: usize) {
137 self.entries.truncate(from_line);
138 }
139
140 pub fn rebuild_from(&mut self, markdown: &Markdown, from_node_idx: usize) {
142 let mut current_line = if let Some(entry) = self.entries.last() {
143 entry.visual_line + 1
144 } else {
145 0
146 };
147
148 for (idx, node) in markdown.nodes.iter().enumerate().skip(from_node_idx) {
149 Self::process_node(self, node, idx, &mut current_line);
150 }
151 }
152}
153
154impl Default for LineMap {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_cursor_creation() {
166 let cursor = Cursor::new();
167 assert_eq!(cursor.line, 0);
168 assert_eq!(cursor.column, 0);
169 assert_eq!(cursor.desired_column, 0);
170 }
171
172 #[test]
173 fn test_cursor_with_position() {
174 let cursor = Cursor::with_position(5, 10);
175 assert_eq!(cursor.line, 5);
176 assert_eq!(cursor.column, 10);
177 assert_eq!(cursor.desired_column, 10);
178 }
179
180 #[test]
181 fn test_linemap_creation() {
182 let linemap = LineMap::new();
183 assert_eq!(linemap.line_count(), 0);
184 }
185}