1use kode_core::{Editor, Position, Selection, Transaction};
2
3use crate::parse::MarkdownTree;
4
5pub struct MarkdownEditor {
10 editor: Editor,
11 tree: MarkdownTree,
12}
13
14impl MarkdownEditor {
15 pub fn new(text: &str) -> Self {
17 Self {
18 editor: Editor::new(text),
19 tree: MarkdownTree::new(text),
20 }
21 }
22
23 pub fn empty() -> Self {
25 Self::new("")
26 }
27
28 pub fn editor(&self) -> &Editor {
32 &self.editor
33 }
34
35 pub fn editor_mut(&mut self) -> &mut Editor {
42 &mut self.editor
43 }
44
45 pub fn buffer(&self) -> &kode_core::Buffer {
47 self.editor.buffer()
48 }
49
50 pub fn version(&self) -> u64 {
52 self.editor.version()
53 }
54
55 pub fn tree(&self) -> &MarkdownTree {
57 &self.tree
58 }
59
60 pub fn sync_tree(&mut self) {
63 self.tree.set_source(&self.editor.text());
64 }
65
66 pub fn insert(&mut self, text: &str) {
70 self.editor.insert(text);
71 self.sync_tree();
72 }
73
74 pub fn backspace(&mut self) {
76 self.editor.backspace();
77 self.sync_tree();
78 }
79
80 pub fn delete_forward(&mut self) {
82 self.editor.delete_forward();
83 self.sync_tree();
84 }
85
86 pub fn insert_newline(&mut self) {
88 self.editor.insert_newline();
89 self.sync_tree();
90 }
91
92 pub fn undo(&mut self) {
94 self.editor.undo();
95 self.sync_tree();
96 }
97
98 pub fn redo(&mut self) {
100 self.editor.redo();
101 self.sync_tree();
102 }
103
104 pub fn delete_selection(&mut self) {
106 self.editor.delete_selection();
107 self.sync_tree();
108 }
109
110 pub fn apply_transaction(&mut self, tx: Transaction) {
112 self.editor.apply_transaction(tx);
113 self.sync_tree();
114 }
115
116 pub fn indent(&mut self) {
118 self.editor.indent();
119 self.sync_tree();
120 }
121
122 pub fn outdent(&mut self) {
124 self.editor.outdent();
125 self.sync_tree();
126 }
127
128 pub fn duplicate_lines(&mut self) {
130 self.editor.duplicate_lines();
131 self.sync_tree();
132 }
133
134 pub fn delete_word_back(&mut self) {
136 self.editor.delete_word_back();
137 self.sync_tree();
138 }
139
140 pub fn delete_word_forward(&mut self) {
142 self.editor.delete_word_forward();
143 self.sync_tree();
144 }
145
146 pub fn text(&self) -> String {
150 self.editor.text()
151 }
152
153 pub fn cursor(&self) -> Position {
155 self.editor.cursor()
156 }
157
158 pub fn selection(&self) -> Selection {
160 self.editor.selection()
161 }
162
163 pub fn selected_text(&self) -> String {
165 self.editor.selected_text()
166 }
167
168 pub fn set_cursor(&mut self, pos: Position) {
170 self.editor.set_cursor(pos);
171 }
172
173 pub fn set_selection(&mut self, anchor: Position, head: Position) {
175 self.editor.set_selection(anchor, head);
176 }
177
178 pub fn select_all(&mut self) {
180 self.editor.select_all();
181 }
182
183 pub fn move_left(&mut self) {
187 self.editor.move_left();
188 }
189
190 pub fn move_right(&mut self) {
192 self.editor.move_right();
193 }
194
195 pub fn move_up(&mut self) {
197 self.editor.move_up();
198 }
199
200 pub fn move_down(&mut self) {
202 self.editor.move_down();
203 }
204
205 pub fn move_word_left(&mut self) {
207 self.editor.move_word_left();
208 }
209
210 pub fn move_word_right(&mut self) {
212 self.editor.move_word_right();
213 }
214
215 pub fn move_to_line_start(&mut self) {
217 self.editor.move_to_line_start();
218 }
219
220 pub fn move_to_line_end(&mut self) {
222 self.editor.move_to_line_end();
223 }
224
225 pub fn move_to_start(&mut self) {
227 self.editor.move_to_start();
228 }
229
230 pub fn move_to_end(&mut self) {
232 self.editor.move_to_end();
233 }
234
235 pub fn extend_selection_left(&mut self) {
239 self.editor.extend_selection_left();
240 }
241
242 pub fn extend_selection_right(&mut self) {
244 self.editor.extend_selection_right();
245 }
246
247 pub fn extend_selection_up(&mut self) {
249 self.editor.extend_selection_up();
250 }
251
252 pub fn extend_selection_down(&mut self) {
254 self.editor.extend_selection_down();
255 }
256
257 pub fn extend_selection(&mut self, head: Position) {
259 self.editor.extend_selection(head);
260 }
261
262 pub fn extend_selection_word_left(&mut self) {
264 self.editor.extend_selection_word_left();
265 }
266
267 pub fn extend_selection_word_right(&mut self) {
269 self.editor.extend_selection_word_right();
270 }
271
272 pub fn extend_selection_to_line_start(&mut self) {
274 self.editor.extend_selection_to_line_start();
275 }
276
277 pub fn extend_selection_to_line_end(&mut self) {
279 self.editor.extend_selection_to_line_end();
280 }
281
282 pub fn extend_selection_to_start(&mut self) {
284 self.editor.extend_selection_to_start();
285 }
286
287 pub fn extend_selection_to_end(&mut self) {
289 self.editor.extend_selection_to_end();
290 }
291
292 pub fn select_word(&mut self) {
296 self.editor.select_word();
297 }
298
299 pub fn select_line(&mut self) {
301 self.editor.select_line();
302 }
303
304 pub fn page_up(&mut self, page_lines: usize) {
306 self.editor.page_up(page_lines);
307 }
308
309 pub fn page_down(&mut self, page_lines: usize) {
311 self.editor.page_down(page_lines);
312 }
313
314 pub fn is_dirty(&self) -> bool {
318 self.editor.is_dirty()
319 }
320
321 pub fn mark_clean(&mut self) {
323 self.editor.mark_clean();
324 }
325
326 pub fn can_undo(&self) -> bool {
328 self.editor.can_undo()
329 }
330
331 pub fn can_redo(&self) -> bool {
333 self.editor.can_redo()
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use crate::{InputRules, MarkdownCommands, NodeKind};
341
342 #[test]
343 fn insert_text_syncs_editor_and_tree() {
344 let mut md = MarkdownEditor::new("");
345 md.insert("# Hello");
346 assert_eq!(md.editor().text(), md.tree().source());
347 assert_eq!(md.editor().text(), "# Hello");
348 }
349
350 #[test]
351 fn undo_reverts_both_editor_and_tree() {
352 let mut md = MarkdownEditor::new("");
353 md.insert("# Hello");
354 assert_eq!(md.text(), "# Hello");
355 assert_eq!(md.tree().source(), "# Hello");
356
357 md.undo();
358 assert_eq!(md.editor().text(), "");
359 assert_eq!(md.tree().source(), "");
360 assert_eq!(md.editor().text(), md.tree().source());
361 }
362
363 #[test]
364 fn walk_blocks_returns_correct_blocks_after_edits() {
365 let mut md = MarkdownEditor::new("# Title");
366 md.set_cursor(Position::new(0, 7));
368 md.insert_newline();
369 md.insert_newline();
370 md.insert("Some paragraph text.");
371
372 let mut blocks = Vec::new();
373 md.tree().walk_blocks(|info| blocks.push(info.kind));
374
375 assert!(
376 blocks.contains(&NodeKind::Heading { level: 1 }),
377 "Expected heading block, got: {:?}",
378 blocks
379 );
380 assert!(
381 blocks.contains(&NodeKind::Paragraph),
382 "Expected paragraph block, got: {:?}",
383 blocks
384 );
385 }
386
387 #[test]
388 fn toggle_bold_via_editor_mut_and_sync_tree() {
389 let mut md = MarkdownEditor::new("hello world");
390 md.set_selection(Position::new(0, 6), Position::new(0, 11));
392 MarkdownCommands::toggle_bold(md.editor_mut());
393 md.sync_tree();
394
395 assert_eq!(md.editor().text(), "hello **world**");
396 assert_eq!(md.tree().source(), "hello **world**");
397 assert_eq!(md.editor().text(), md.tree().source());
398 }
399
400 #[test]
401 fn input_rules_handle_enter_via_editor_mut_and_sync_tree() {
402 let mut md = MarkdownEditor::new("- item 1");
403 md.set_cursor(Position::new(0, 8));
404 let handled = InputRules::handle_enter(md.editor_mut());
405 md.sync_tree();
406
407 assert!(handled);
408 assert_eq!(md.editor().text(), "- item 1\n- ");
409 assert_eq!(md.tree().source(), "- item 1\n- ");
410 assert_eq!(md.editor().text(), md.tree().source());
411 }
412
413 #[test]
414 fn backspace_syncs_tree() {
415 let mut md = MarkdownEditor::new("abc");
416 md.set_cursor(Position::new(0, 3));
417 md.backspace();
418 assert_eq!(md.editor().text(), "ab");
419 assert_eq!(md.tree().source(), "ab");
420 }
421
422 #[test]
423 fn delete_forward_syncs_tree() {
424 let mut md = MarkdownEditor::new("abc");
425 md.set_cursor(Position::new(0, 0));
426 md.delete_forward();
427 assert_eq!(md.editor().text(), "bc");
428 assert_eq!(md.tree().source(), "bc");
429 }
430
431 #[test]
432 fn redo_syncs_tree() {
433 let mut md = MarkdownEditor::new("");
434 md.insert("# Test");
435 md.undo();
436 assert_eq!(md.tree().source(), "");
437
438 md.redo();
439 assert_eq!(md.editor().text(), "# Test");
440 assert_eq!(md.tree().source(), "# Test");
441 }
442
443 #[test]
444 fn delete_selection_syncs_tree() {
445 let mut md = MarkdownEditor::new("hello world");
446 md.set_selection(Position::new(0, 5), Position::new(0, 11));
447 md.delete_selection();
448 assert_eq!(md.editor().text(), "hello");
449 assert_eq!(md.tree().source(), "hello");
450 }
451
452 #[test]
453 fn indent_outdent_sync_tree() {
454 let mut md = MarkdownEditor::new("line1\nline2");
455 md.set_selection(Position::new(0, 0), Position::new(1, 5));
456 md.indent();
457 assert_eq!(md.editor().text(), md.tree().source());
458 assert_eq!(md.editor().text(), " line1\n line2");
459
460 md.set_selection(Position::new(0, 0), Position::new(1, 7));
462 md.outdent();
463 assert_eq!(md.editor().text(), "line1\nline2");
464 assert_eq!(md.tree().source(), "line1\nline2");
465 }
466
467 #[test]
468 fn duplicate_lines_syncs_tree() {
469 let mut md = MarkdownEditor::new("line1\nline2");
470 md.set_cursor(Position::new(0, 0));
471 md.duplicate_lines();
472 assert_eq!(md.editor().text(), md.tree().source());
473 assert_eq!(md.editor().text(), "line1\nline1\nline2");
474 }
475
476 #[test]
477 fn apply_transaction_syncs_tree() {
478 let mut md = MarkdownEditor::new("Hello\nWorld");
479 let tx = Transaction::new(vec![
480 kode_core::EditStep::replace(0, "Hello".to_string(), "> Hello".to_string()),
481 kode_core::EditStep::replace(8, "World".to_string(), "> World".to_string()),
482 ]);
483 md.apply_transaction(tx);
484 assert_eq!(md.editor().text(), "> Hello\n> World");
485 assert_eq!(md.tree().source(), "> Hello\n> World");
486 }
487
488 #[test]
489 fn delete_word_back_syncs_tree() {
490 let mut md = MarkdownEditor::new("hello world");
491 md.set_cursor(Position::new(0, 11));
492 md.delete_word_back();
493 assert_eq!(md.editor().text(), "hello ");
494 assert_eq!(md.tree().source(), "hello ");
495 }
496
497 #[test]
498 fn delete_word_forward_syncs_tree() {
499 let mut md = MarkdownEditor::new("hello world");
500 md.set_cursor(Position::new(0, 0));
501 md.delete_word_forward();
502 assert_eq!(md.editor().text(), "world");
503 assert_eq!(md.tree().source(), "world");
504 }
505
506 #[test]
507 fn read_only_methods_work() {
508 let md = MarkdownEditor::new("# Hello");
509 assert_eq!(md.text(), "# Hello");
510 assert_eq!(md.cursor(), Position::zero());
511 assert!(md.selection().is_cursor());
512 assert_eq!(md.selected_text(), "");
513 assert!(!md.is_dirty());
514 assert!(!md.can_undo());
515 assert!(!md.can_redo());
516 }
517
518 #[test]
519 fn movement_methods_work() {
520 let mut md = MarkdownEditor::new("hello world\nsecond line");
521 md.move_right();
522 assert_eq!(md.cursor(), Position::new(0, 1));
523 md.move_to_line_end();
524 assert_eq!(md.cursor(), Position::new(0, 11));
525 md.move_down();
526 assert_eq!(md.cursor(), Position::new(1, 11));
527 md.move_to_line_start();
528 assert_eq!(md.cursor(), Position::new(1, 0));
529 md.move_left();
530 assert_eq!(md.cursor(), Position::new(0, 11));
535 md.move_to_start();
536 assert_eq!(md.cursor(), Position::zero());
537 md.move_to_end();
538 assert_eq!(md.cursor(), Position::new(1, 11));
539 md.move_to_start();
540 md.move_up();
541 assert_eq!(md.cursor(), Position::zero());
543 }
544}