use std::cell::RefCell;
use editor::{AppExt as _, Editor};
use gpui::{
AnyElement, App, Entity, FocusHandle, Focusable, TestAppContext, VisualTestContext, Window,
div, prelude::*, px, size,
};
use markdown::{AppExt as _, BlockKind, Fence, Text};
thread_local! {
static INSIDE: RefCell<Option<FocusHandle>> = const { RefCell::new(None) };
}
fn paint(fence: &Fence<'_>, _: &mut Window, cx: &mut App) -> Option<AnyElement> {
if fence.language != "widget" {
return None;
}
let handle = INSIDE.with(|inside| {
inside
.borrow_mut()
.get_or_insert_with(|| cx.focus_handle())
.clone()
});
Some(
div()
.id("widget")
.h(px(40.0))
.track_focus(&handle)
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
.into_any_element(),
)
}
fn open(source: &str, cx: &mut TestAppContext) -> (Entity<Editor>, VisualTestContext) {
cx.update(|cx| {
theme::Theme::install(theme::Appearance::Dark, cx);
editor::init(cx);
cx.set_block_renderer(paint);
let mut items = editor::slash_defaults();
items.push(editor::SlashItem::Group {
label: "Widget".into(),
icon: None,
rows: vec![
editor::SlashRow {
label: "Blue".into(),
icon: None,
action: editor::SlashAction::Run(std::rc::Rc::new(|at, _, cx| {
let kind = BlockKind::Code {
language: Some("widget".to_owned()),
code: Text::plain("blue"),
height: None,
};
at.editor
.update(cx, |editor, cx| editor.place_block(at.block, kind, cx))
.ok();
})),
},
editor::SlashRow {
label: "Green".into(),
icon: None,
action: editor::SlashAction::Block(BlockKind::Paragraph(Text::plain("green"))),
},
],
});
cx.set_slash_items(items);
});
let window = cx.add_window(|_, cx| Editor::new(source, cx));
let editor = window.root(cx).unwrap();
let mut visual = VisualTestContext::from_window(window.into(), cx);
visual.simulate_resize(size(px(360.0), px(600.0)));
visual.update(|window, cx| {
let handle = editor.read(cx).focus_handle(cx);
window.focus(&handle, cx);
});
visual.run_until_parked();
(editor, visual)
}
fn source(editor: &Entity<Editor>, cx: &mut VisualTestContext) -> String {
cx.update(|_, cx| editor.read(cx).source())
}
#[gpui::test]
fn an_app_row_makes_its_block_and_leaves_the_caret_after_it(cx: &mut TestAppContext) {
let (editor, mut cx) = open("# Title", cx);
cx.simulate_keystrokes("end enter");
cx.simulate_input("/blue");
cx.simulate_keystrokes("enter");
cx.run_until_parked();
cx.simulate_input("after");
assert_eq!(
source(&editor, &mut cx),
"# Title\n\n```widget\nblue\n```\n\nafter"
);
}
#[gpui::test]
fn set_code_rewrites_the_fence(cx: &mut TestAppContext) {
let (editor, mut cx) = open("```widget\n```\n", cx);
cx.update(|_, cx| editor.update(cx, |editor, cx| editor.set_code(0, "link".to_owned(), cx)));
assert_eq!(source(&editor, &mut cx), "```widget\nlink\n```\n");
}
#[gpui::test]
fn keys_inside_a_painted_fence_stay_out_of_the_document(cx: &mut TestAppContext) {
let (editor, mut cx) = open("text\n\n```widget\n```\n", cx);
let inside = INSIDE
.with(|inside| inside.borrow().clone())
.expect("painted");
let before = source(&editor, &mut cx);
cx.update(|window, cx| window.focus(&inside, cx));
cx.run_until_parked();
cx.simulate_keystrokes("backspace enter cmd-a");
assert_eq!(source(&editor, &mut cx), before);
cx.simulate_keystrokes("escape");
let focused = cx.update(|window, cx| editor.read(cx).focus_handle(cx).is_focused(window));
assert!(focused, "escape hands the keyboard back to the document");
}
#[gpui::test]
fn a_query_matching_a_group_keeps_its_rows_behind_it(cx: &mut TestAppContext) {
let (editor, mut cx) = open("# Title", cx);
cx.simulate_keystrokes("end enter");
cx.simulate_input("/widget");
cx.simulate_keystrokes("enter enter");
cx.run_until_parked();
assert_eq!(source(&editor, &mut cx), "# Title\n\n```widget\nblue\n```");
}
#[gpui::test]
fn a_painted_fence_is_dragged_taller_by_its_bottom_edge(cx: &mut TestAppContext) {
let (editor, mut cx) = open("text\n\n```widget\n```\n", cx);
for _ in 0..3 {
cx.update(|window, _| window.refresh());
cx.run_until_parked();
}
let widget = cx
.update(|_, cx| editor.read(cx).layouts().painted_bounds(1))
.expect("painted");
cx.simulate_mouse_move(widget.center(), None, gpui::Modifiers::default());
cx.update(|window, _| window.refresh());
cx.run_until_parked();
let handle = cx
.debug_bounds("painted-grip")
.expect("hovering a painted fence shows its height handle");
let from = handle.center();
cx.simulate_mouse_move(from, None, gpui::Modifiers::default());
cx.run_until_parked();
let to = from + gpui::point(px(0.0), px(160.0));
cx.simulate_mouse_down(from, gpui::MouseButton::Left, gpui::Modifiers::default());
cx.simulate_mouse_move(to, gpui::MouseButton::Left, gpui::Modifiers::default());
cx.simulate_mouse_up(to, gpui::MouseButton::Left, gpui::Modifiers::default());
cx.run_until_parked();
let source = source(&editor, &mut cx);
assert!(source.contains("```widget 2"), "{source}");
}