165 lines
5.8 KiB
Rust
165 lines
5.8 KiB
Rust
//! Outliner: moving list items with their children, and shifting subtrees.
|
|
|
|
use std::fmt::Write as _;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use notescraft_editor::keys::{Edit, Indent};
|
|
use notescraft_editor::{Direction, indent_subtree, move_item};
|
|
|
|
/// The text with the edit applied.
|
|
fn applied(text: &str, edit: &Edit) -> String {
|
|
let mut out = String::from(text);
|
|
out.replace_range(edit.range.clone(), &edit.insert);
|
|
out
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_moves_an_item_down_and_up() {
|
|
let text = "- a\n- b\n- c\n";
|
|
let edit = move_item(text, 4..4, Direction::Down, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- a\n- c\n- b\n");
|
|
let edit = move_item(text, 8..8, Direction::Up, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- a\n- c\n- b\n");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_moves_the_children_with_their_item() {
|
|
let text = "- a\n - a1\n- b\n";
|
|
let edit = move_item(text, 0..0, Direction::Down, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- b\n- a\n - a1\n");
|
|
|
|
let text = "- a\n - a1\n - a2\n - a3\n- b\n";
|
|
let edit = move_item(text, 0..0, Direction::Down, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- b\n- a\n - a1\n - a2\n - a3\n");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_moves_across_a_task_item() {
|
|
let text = "- a\n- [ ] task\n- c\n";
|
|
let at = text.find("- c").unwrap();
|
|
let edit = move_item(text, at..at, Direction::Up, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- a\n- c\n- [ ] task\n");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_renumbers_an_ordered_list() {
|
|
let text = "1. a\n2. b\n3. c\n";
|
|
let at = text.find("3. c").unwrap();
|
|
let edit = move_item(text, at..at, Direction::Up, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "1. a\n2. c\n3. b\n");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_leaves_the_first_and_the_last_item_alone() {
|
|
let text = "- a\n- b\n";
|
|
assert!(move_item(text, 0..0, Direction::Up, Indent::default()).is_none());
|
|
assert!(move_item(text, 4..4, Direction::Down, Indent::default()).is_none());
|
|
assert!(move_item(text, 0..0, Direction::Down, Indent::default()).is_some());
|
|
assert!(move_item(text, 4..4, Direction::Up, Indent::default()).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_moves_a_fenced_code_block_with_its_item() {
|
|
let text = "- a\n ```\n code\n ```\n- b\n";
|
|
let edit = move_item(text, 0..0, Direction::Down, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- b\n- a\n ```\n code\n ```\n");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_keeps_crlf_line_endings() {
|
|
let text = "- a\r\n- b\r\n";
|
|
let edit = move_item(text, 0..0, Direction::Down, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- b\r\n- a\r\n");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_moves_items_indented_with_tabs_and_with_spaces() {
|
|
let text = "- a\n\t- b\n- c\n";
|
|
let edit = move_item(text, 0..0, Direction::Down, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- c\n- a\n\t- b\n");
|
|
|
|
let text = "- a\n - b\n- c\n";
|
|
let edit = move_item(text, 0..0, Direction::Down, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- c\n- a\n - b\n");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_keeps_the_cursor_on_the_moved_marker() {
|
|
let text = "- a\n- b\n- c\n";
|
|
let edit = move_item(text, 4..4, Direction::Down, Indent::default()).unwrap();
|
|
assert_eq!(edit.sel, 8..8);
|
|
assert_eq!(applied(text, &edit).get(8..9), Some("-"));
|
|
|
|
let text = "1. a\n2. b\n3. c\n";
|
|
let at = text.find("3. c").unwrap();
|
|
let edit = move_item(text, at..at, Direction::Up, Indent::default()).unwrap();
|
|
assert_eq!(edit.sel, 5..5);
|
|
assert_eq!(applied(text, &edit).get(5..6), Some("2"));
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_moves_the_first_item_of_a_selection() {
|
|
let text = "- a\n- b\n- c\n";
|
|
let edit = move_item(text, 0..8, Direction::Down, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- b\n- a\n- c\n");
|
|
assert_eq!(edit.sel, 4..4);
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_moves_a_long_list_quickly() {
|
|
let mut text = String::new();
|
|
for index in 0..2000 {
|
|
writeln!(text, "- item {index}").unwrap();
|
|
}
|
|
let start = Instant::now();
|
|
let edit = move_item(&text, 0..0, Direction::Down, Indent::default());
|
|
let elapsed = start.elapsed();
|
|
assert!(edit.is_some());
|
|
assert!(elapsed < Duration::from_millis(5), "moving an item took {elapsed:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_indents_an_item_with_its_children() {
|
|
let text = "- a\n - b\n - c\n- d\n";
|
|
let edit = indent_subtree(text, 0..0, false, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), " - a\n - b\n - c\n- d\n");
|
|
assert_eq!(edit.sel, 4..4, "the cursor stays on its character");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_outdents_an_item_with_its_children() {
|
|
let text = " - a\n - b\n- c\n";
|
|
let at = text.find("- a").unwrap();
|
|
let edit = indent_subtree(text, at..at, true, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), "- a\n - b\n- c\n");
|
|
assert_eq!(edit.sel, 0..0);
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_refuses_to_outdent_below_the_surface_of_the_list() {
|
|
let text = "- a\n- b\n";
|
|
assert!(indent_subtree(text, 0..0, true, Indent::default()).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_indents_with_the_published_indent_unit() {
|
|
let text = "- a\n";
|
|
let indent = Indent { tabs: true, width: 4 };
|
|
let edit = indent_subtree(text, 0..0, false, indent).unwrap();
|
|
assert_eq!(applied(text, &edit), "\t- a\n");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_shifts_every_item_a_selection_touches() {
|
|
let text = "- a\n- b\n";
|
|
let edit = indent_subtree(text, 0..8, false, Indent::default()).unwrap();
|
|
assert_eq!(applied(text, &edit), " - a\n - b\n");
|
|
}
|
|
|
|
#[test]
|
|
fn outliner_ignores_text_outside_a_list_item() {
|
|
let text = "plain words\n";
|
|
assert!(indent_subtree(text, 0..0, false, Indent::default()).is_none());
|
|
assert!(move_item(text, 0..0, Direction::Down, Indent::default()).is_none());
|
|
}
|