use itertools::Itertools; use markdown_parser::parse_markdown; use string_offset::CharOffset; use galaxy_core::features::FeatureFlag; use crate::content::{ buffer::{Buffer, EditOrigin}, selection_model::BufferSelectionModel, text::IndentBehavior, }; use galaxyui::{ App, text::{TextBuffer, point::Point, word_boundaries::WordBoundariesPolicy}, }; #[test] fn test_forward_iteration() { App::test((), |mut app| async move { let buffer = app.add_model(|_| Buffer::new(Box::new(|_, _| IndentBehavior::Ignore))); let selection = app.add_model(|_| BufferSelectionModel::new(buffer.clone())); buffer.update(&mut app, |buffer, ctx| { buffer.replace_with_formatted_text( CharOffset::from(0)..CharOffset::from(1), parse_markdown("```\nText\n```\n**bold**\nAnd *italic* too.") .expect("Markdown should parse"), EditOrigin::UserInitiated, selection.clone(), ctx, ); assert_eq!( buffer.debug(), "Textbold\\nAnd italic too." ); let mut chars = buffer.chars_at(CharOffset::from(1)).expect("Offset valid"); // Block markers are converted to whitespace. assert_eq!(chars.next(), Some('T')); assert_eq!(chars.next(), Some('e')); assert_eq!(chars.next(), Some('x')); assert_eq!(chars.next(), Some('t')); assert_eq!(chars.next(), Some('\n')); // This transparently skips over the style markers. assert_eq!(chars.next(), Some('b')); // We should also be able to start from partway through. let chars = buffer .chars_at(CharOffset::from(12)) .expect("Offset valid") .collect_vec(); assert_eq!( chars, vec![ 'n', 'd', ' ', 'i', 't', 'a', 'l', 'i', 'c', ' ', 't', 'o', 'o', '.' ] ); }); }); } #[test] fn test_table_word_boundaries_include_full_cell_text() { App::test((), |mut app| async move { let _flag = FeatureFlag::MarkdownTables.override_enabled(true); let (buffer, _selection) = Buffer::mock_from_markdown( "| Hello | Value |\n| --- | --- |\n| World | Cell |\n", None, Box::new(|_, _| IndentBehavior::Ignore), &mut app, ); buffer.read(&app, |buffer, _| { let chars = buffer .chars_at(CharOffset::from(1)) .expect("Offset valid") .take(12) .collect_vec(); assert_eq!( chars, vec!['H', 'e', 'l', 'l', 'o', '\t', 'V', 'a', 'l', 'u', 'e', '\n'] ); let policy = WordBoundariesPolicy::Default; let start = buffer.word_start(CharOffset::from(1), &policy); let end = buffer.word_end(CharOffset::from(1), &policy); assert_eq!(start, CharOffset::from(1)); assert_eq!(buffer.text_in_range(start..end).into_string(), "Hello"); }); }); } #[test] fn test_start_styled() { App::test((), |mut app| async move { let buffer = app.add_model(|_| Buffer::new(Box::new(|_, _| IndentBehavior::Ignore))); let selection = app.add_model(|_| BufferSelectionModel::new(buffer.clone())); buffer.update(&mut app, |buffer, ctx| { buffer.replace_with_formatted_text( CharOffset::from(0)..CharOffset::from(1), parse_markdown("*styled* text").expect("Markdown should parse"), EditOrigin::UserInitiated, selection.clone(), ctx, ); let mut chars = buffer.chars_at(CharOffset::from(1)).expect("Offset valid"); assert_eq!(chars.next(), Some('s')); assert_eq!(chars.next(), Some('t')); }); }); } #[test] fn test_reverse_iteration() { App::test((), |mut app| async move { let buffer = app.add_model(|_| Buffer::new(Box::new(|_, _| IndentBehavior::Ignore))); let selection = app.add_model(|_| BufferSelectionModel::new(buffer.clone())); buffer.update(&mut app, |buffer, ctx| { buffer.replace_with_formatted_text( CharOffset::from(0)..CharOffset::from(1), parse_markdown("some *text*").expect("Markdown should parse"), EditOrigin::UserInitiated, selection.clone(), ctx, ); let chars = buffer .chars_rev_at(CharOffset::from(4)) .expect("Offset valid") .collect_vec(); assert_eq!(chars, vec!['m', 'o', 's', '\n']); let chars = buffer .chars_rev_at(CharOffset::from(0)) .expect("Offset valid") .collect_vec(); assert!(chars.is_empty()); let mut chars = buffer .chars_rev_at(CharOffset::from(8)) .expect("Offset valid"); assert_eq!(chars.next(), Some('e')); assert_eq!(chars.next(), Some('t')); assert_eq!(chars.next(), Some(' ')); assert_eq!(chars.next(), Some('e')); }); }); } #[test] fn test_plain_text_boundaries() { App::test((), |mut app| async move { let buffer = app.add_model(|_| Buffer::new(Box::new(|_, _| IndentBehavior::Ignore))); let selection = app.add_model(|_| BufferSelectionModel::new(buffer.clone())); buffer.update(&mut app, |buffer, ctx| { buffer.replace_with_formatted_text( CharOffset::from(0)..CharOffset::from(1), parse_markdown("this *is* plain\ntext").expect("Markdown should parse"), EditOrigin::UserInitiated, selection.clone(), ctx, ); let offsets = buffer .word_starts_from_offset(CharOffset::from(3)) .unwrap() .collect_vec(); assert_eq!( offsets, vec![ Point::new(1, 5), Point::new(1, 8), Point::new(2, 0), Point::new(2, 4) ] ); let ends_exclusive = buffer .word_ends_from_offset_exclusive(CharOffset::from(5)) .unwrap() .collect_vec(); // This should exclude the end of "this". assert_eq!( ends_exclusive, vec![Point::new(1, 7), Point::new(1, 13), Point::new(2, 4)] ); }); }); } #[test] fn test_empty_buffer() { App::test((), |mut app| async move { let buffer = app.add_model(|_| Buffer::new(Box::new(|_, _| IndentBehavior::Ignore))); let _selection = app.add_model(|_| BufferSelectionModel::new(buffer.clone())); buffer.read(&app, |buffer, _| { // Clearly out-of-bounds indices should fail. assert!(buffer.chars_at(4.into()).is_err()); assert!(buffer.chars_rev_at(4.into()).is_err()); // Starting at 0, on the other hand, produces an empty iterator. // All buffers implicitly contain a leading block marker. However, when segmenting words, the // marker should not be included. let mut chars = buffer.chars_at(CharOffset::zero()).expect("Can start at 0"); assert_eq!(chars.next(), Some('\n')); let mut chars = buffer .chars_rev_at(CharOffset::zero()) .expect("Can start at 0"); assert_eq!(chars.next(), None); let mut words = buffer .word_starts_from_offset(CharOffset::zero()) .expect("Can start at 0"); // Since the buffer is not truly empty (due to the leading block marker), WordBoundaries::next // considers the end of the buffer a word boundary. assert_eq!(words.next(), Some(Point::new(1, 0))); // Likewise, when moving backwards, the start of the buffer is a word boundary. let mut words_rev = buffer .word_starts_backward_from_offset_exclusive(CharOffset::zero()) .expect("Can start at 0"); assert_eq!(words_rev.next(), Some(Point::new(0, 0))); }); }); }