use markdown::{BlockLayouts, Doc, parse::parse, selectable::copied, *};
fn body(block: usize, offset: usize) -> Cursor {
Cursor::new(block, Part::Body, offset)
}
const DOC: &str = "# Title\n\nA paragraph with **bold** in it.\n\n- first\n- second";
#[test]
fn a_selection_inside_one_block_is_the_text_between_its_ends() {
let doc = parse(DOC);
let selection = Selection::new(body(1, 2), body(1, 11));
assert_eq!(copied(&doc, selection), "paragraph");
}
#[test]
fn crossing_blocks_puts_a_newline_where_each_one_ended() {
let doc = parse(DOC);
let selection = Selection::new(body(2, 0), body(3, 6));
assert_eq!(copied(&doc, selection), "first\nsecond");
}
#[test]
fn what_comes_out_is_text_rather_than_markup() {
let doc = parse(DOC);
let whole = Selection::new(body(0, 0), body(1, 30));
let text = copied(&doc, whole);
assert!(
!text.contains('*'),
"marks are not part of the text: {text:?}"
);
assert!(text.starts_with("Title\n"), "{text:?}");
}
#[test]
fn a_press_that_never_dragged_copies_nothing() {
let doc = parse(DOC);
assert_eq!(copied(&doc, Selection::at(body(1, 4))), "");
}
#[gpui::test]
fn surface_takes_focus_and_copies_selected_plain_text(cx: &mut gpui::TestAppContext) {
use gpui::{Context, Render, Window, prelude::*};
struct Reader {
focus: gpui::FocusHandle,
}
impl Render for Reader {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
selectable::surface(
&self.focus,
&parse("hello **world**"),
Some(Selection::new(body(0, 6), body(0, 11))),
cx,
)
.id("reader")
.debug_selector(|| "reader".into())
.size_full()
.child("hello world")
}
}
let window = cx.add_window(|_, cx| Reader {
focus: cx.focus_handle(),
});
let mut visual = gpui::VisualTestContext::from_window(window.into(), cx);
visual.run_until_parked();
let point = visual.debug_bounds("reader").unwrap().center();
visual.simulate_click(point, gpui::Modifiers::default());
visual.run_until_parked();
visual.simulate_keystrokes(if cfg!(target_os = "macos") {
"cmd-c"
} else {
"ctrl-c"
});
visual.update(|_, cx| {
assert_eq!(
cx.read_from_clipboard()
.and_then(|item| item.text())
.as_deref(),
Some("world")
)
});
}
#[gpui::test]
fn a_drag_off_the_text_keeps_extending_the_selection(cx: &mut gpui::TestAppContext) {
use gpui::{Context, Modifiers, MouseButton, Render, Window, div, prelude::*, px, size};
const TEXT: &str = "a paragraph long enough to drag across and then leave";
struct Reader {
doc: Doc,
layouts: BlockLayouts,
selection: Option<Selection>,
dragging: bool,
}
impl Render for Reader {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.id("page")
.debug_selector(|| "page".into())
.size_full()
.pt(px(100.))
.pl(px(100.))
.child(selectable::render(
"reader",
&self.doc,
&self.layouts.clone(),
self.selection,
self.dragging,
window,
cx,
|view: &mut Reader, pointer, cx| {
match pointer {
selectable::Pointer::Down(cursor) => {
view.selection = Some(Selection::at(cursor));
view.dragging = true;
}
selectable::Pointer::Move(cursor) => {
view.selection = view.selection.map(|had| had.extend_to(cursor));
}
selectable::Pointer::Up => view.dragging = false,
}
cx.notify();
},
))
}
}
cx.update(|cx| theme::Theme::install(theme::Appearance::Dark, cx));
let window = cx.add_window(|_, _| Reader {
doc: parse(TEXT),
layouts: BlockLayouts::default(),
selection: None,
dragging: false,
});
let reader = window.root(cx).unwrap();
let mut visual = gpui::VisualTestContext::from_window(window.into(), cx);
visual.simulate_resize(size(px(600.), px(400.)));
visual.run_until_parked();
let page = visual.debug_bounds("page").expect("the page painted");
let start = gpui::point(page.left() + px(105.), page.top() + px(110.));
visual.simulate_mouse_move(start, None, Modifiers::default());
visual.simulate_mouse_down(start, MouseButton::Left, Modifiers::default());
visual.run_until_parked();
assert!(
visual.update(|_, cx| reader.read(cx).dragging),
"the press started a drag"
);
visual.simulate_mouse_move(
gpui::point(px(560.), px(360.)),
Some(MouseButton::Left),
Modifiers::default(),
);
visual.run_until_parked();
let selection = visual
.update(|_, cx| reader.read(cx).selection)
.expect("the press left a selection");
assert!(
!selection.is_collapsed(),
"a move off the text extends what the press started"
);
assert_eq!(
selection.head,
Cursor::new(0, Part::Body, TEXT.len()),
"past the last line resolves to the end of the text"
);
}