pub mod editor_lsp_test_context;pub mod editor_test_context; use std::{rc::Rc, sync::LazyLock}; pub use crate::rust_analyzer_ext::expand_macro_recursively;use crate::{ DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, SelectionEffects, Size, display_map::{ Block, BlockPlacement, CustomBlockId, DisplayMap, DisplayRow, DisplaySnapshot, ToDisplayPoint, },};use collections::HashMap;use gpui::{ AppContext as _, Context, Entity, EntityId, Font, FontFeatures, FontStyle, FontWeight, Pixels, VisualTestContext, Window, font, size,};use multi_buffer::{MultiBufferOffset, ToPoint};use pretty_assertions::assert_eq;use project::{Project, project_settings::DiagnosticSeverity};use ui::{App, BorrowAppContext, IntoElement, px};use util::test::{generate_marked_text, marked_text_offsets, marked_text_ranges}; #[cfg(test)]#[ctor::ctor]fn init_logger() { zlog::init_test();} pub fn test_font() -> Font { static TEST_FONT: LazyLock<Font> = LazyLock::new(|| { #[cfg(not(target_os = "windows"))] { font("Helvetica") } #[cfg(target_os = "windows")] { font("Courier New") } }); TEST_FONT.clone()} // Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.#[track_caller]pub fn marked_display_snapshot( text: &str, cx: &mut gpui::App,) -> (DisplaySnapshot, Vec<DisplayPoint>) { let (unmarked_text, markers) = marked_text_offsets(text); let font = Font { family: ".ZedMono".into(), features: FontFeatures::default(), fallbacks: None, weight: FontWeight::default(), style: FontStyle::default(), }; let font_size: Pixels = 14usize.into(); let buffer = MultiBuffer::build_simple(&unmarked_text, cx); let display_map = cx.new(|cx| { DisplayMap::new( buffer, font, font_size, None, 1, 1, FoldPlaceholder::test(), DiagnosticSeverity::Warning, cx, ) }); let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); let markers = markers .into_iter() .map(|offset| MultiBufferOffset(offset).to_display_point(&snapshot)) .collect(); (snapshot, markers)} #[track_caller]pub fn select_ranges( editor: &mut Editor, marked_text: &str, window: &mut Window, cx: &mut Context<Editor>,) { let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true); assert_eq!(editor.text(cx), unmarked_text); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges( text_ranges .into_iter() .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)), ) });} #[track_caller]pub fn assert_text_with_selections( editor: &mut Editor, marked_text: &str, cx: &mut Context<Editor>,) { let (unmarked_text, _text_ranges) = marked_text_ranges(marked_text, true); assert_eq!(editor.text(cx), unmarked_text, "text doesn't match"); let actual = generate_marked_text( &editor.text(cx), &editor .selections .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx)) .into_iter() .map(|range| range.start.0..range.end.0) .collect::<Vec<_>>(), marked_text.contains("«"), ); assert_eq!(actual, marked_text, "Selections don't match");} #[cfg(any(test, feature = "test-support"))]pub(crate) fn build_editor( buffer: Entity<MultiBuffer>, window: &mut Window, cx: &mut Context<Editor>,) -> Editor { Editor::new(EditorMode::full(), buffer, None, window, cx)} pub(crate) fn build_editor_with_project( project: Entity<Project>, buffer: Entity<MultiBuffer>, window: &mut Window, cx: &mut Context<Editor>,) -> Editor { Editor::new(EditorMode::full(), buffer, Some(project), window, cx)} #[derive(Default)]struct TestBlockContent( HashMap<(EntityId, CustomBlockId), Rc<dyn Fn(&mut VisualTestContext) -> String>>,); impl gpui::Global for TestBlockContent {} pub fn set_block_content_for_tests( editor: &Entity<Editor>, id: CustomBlockId, cx: &mut App, f: impl Fn(&mut VisualTestContext) -> String + 'static,) { cx.update_default_global::<TestBlockContent, _>(|bc, _| { bc.0.insert((editor.entity_id(), id), Rc::new(f)) });} pub fn block_content_for_tests( editor: &Entity<Editor>, id: CustomBlockId, cx: &mut VisualTestContext,) -> Option<String> { let f = cx.update(|_, cx| { cx.default_global::<TestBlockContent>() .0 .get(&(editor.entity_id(), id)) .cloned() })?; Some(f(cx))} pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestContext) -> String { editor_content_with_blocks_and_width(editor, px(3000.), cx)} pub fn editor_content_with_blocks_and_width( editor: &Entity<Editor>, width: Pixels, cx: &mut VisualTestContext,) -> String { editor_content_with_blocks_and_size(editor, size(width, px(3000.0)), cx)} pub fn editor_content_with_blocks_and_size( editor: &Entity<Editor>, draw_size: Size<Pixels>, cx: &mut VisualTestContext,) -> String { cx.simulate_resize(draw_size); cx.draw(gpui::Point::default(), draw_size, |_, _| { editor.clone().into_any_element() }); let (snapshot, mut lines, blocks) = editor.update_in(cx, |editor, window, cx| { let snapshot = editor.snapshot(window, cx); let text = editor.display_text(cx); let lines = text.lines().map(|s| s.to_string()).collect::<Vec<String>>(); let blocks = snapshot .blocks_in_range(DisplayRow(0)..snapshot.max_point().row()) .map(|(row, block)| (row, block.clone())) .collect::<Vec<_>>(); (snapshot, lines, blocks) }); for (row, block) in blocks { match block { Block::Custom(custom_block) => { if let BlockPlacement::Near(x) = &custom_block.placement && snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot())) { continue; }; let content = block_content_for_tests(editor, custom_block.id, cx) .expect("block content not found"); // 2: "related info 1 for diagnostic 0" if let Some(height) = custom_block.height { if height == 0 { lines[row.0 as usize - 1].push_str(" § "); lines[row.0 as usize - 1].push_str(&content); } else { let block_lines = content.lines().collect::<Vec<_>>(); assert_eq!(block_lines.len(), height as usize); lines[row.0 as usize].push_str("§ "); lines[row.0 as usize].push_str(block_lines[0].trim_end()); for i in 1..height as usize { if row.0 as usize + i >= lines.len() { lines.push("".to_string()); }; lines[row.0 as usize + i].push_str("§ "); lines[row.0 as usize + i].push_str(block_lines[i].trim_end()); } } } } Block::FoldedBuffer { first_excerpt, height, } => { while lines.len() <= row.0 as usize { lines.push(String::new()); } lines[row.0 as usize].push_str(&cx.update(|_, cx| { format!( "§ {}", first_excerpt .buffer .file() .map(|file| file.file_name(cx)) .unwrap_or("<no file>") ) })); for row in row.0 + 1..row.0 + height { while lines.len() <= row as usize { lines.push(String::new()); } lines[row as usize].push_str("§ -----"); } } Block::ExcerptBoundary { height, .. } => { for row in row.0..row.0 + height { while lines.len() <= row as usize { lines.push(String::new()); } lines[row as usize].push_str("§ -----"); } } Block::BufferHeader { excerpt, height } => { while lines.len() <= row.0 as usize { lines.push(String::new()); } lines[row.0 as usize].push_str(&cx.update(|_, cx| { format!( "§ {}", excerpt .buffer .file() .map(|file| file.file_name(cx)) .unwrap_or("<no file>") ) })); for row in row.0 + 1..row.0 + height { while lines.len() <= row as usize { lines.push(String::new()); } lines[row as usize].push_str("§ -----"); } } Block::Spacer { height, .. } => { for row in row.0..row.0 + height { while lines.len() <= row as usize { lines.push(String::new()); } lines[row as usize].push_str("§ spacer"); } } } } lines.join("\n")}
pub mod editor_lsp_test_context;
pub mod editor_test_context;
use std::{rc::Rc, sync::LazyLock};
pub use crate::rust_analyzer_ext::expand_macro_recursively;
use crate::{
DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, SelectionEffects, Size,
display_map::{
Block, BlockPlacement, CustomBlockId, DisplayMap, DisplayRow, DisplaySnapshot,
ToDisplayPoint,
},
};
use collections::HashMap;
use gpui::{
AppContext as _, Context, Entity, EntityId, Font, FontFeatures, FontStyle, FontWeight, Pixels,
VisualTestContext, Window, font, size,
};
use multi_buffer::{MultiBufferOffset, ToPoint};
use pretty_assertions::assert_eq;
use project::{Project, project_settings::DiagnosticSeverity};
use ui::{App, BorrowAppContext, IntoElement, px};
use util::test::{generate_marked_text, marked_text_offsets, marked_text_ranges};
#[cfg(test)]
#[ctor::ctor]
fn init_logger() {
zlog::init_test();
}
pub fn test_font() -> Font {
static TEST_FONT: LazyLock<Font> = LazyLock::new(|| {
#[cfg(not(target_os = "windows"))]
{
font("Helvetica")
}
#[cfg(target_os = "windows")]
{
font("Courier New")
}
});
TEST_FONT.clone()
}
// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
#[track_caller]
pub fn marked_display_snapshot(
text: &str,
cx: &mut gpui::App,
) -> (DisplaySnapshot, Vec<DisplayPoint>) {
let (unmarked_text, markers) = marked_text_offsets(text);
let font = Font {
family: ".ZedMono".into(),
features: FontFeatures::default(),
fallbacks: None,
weight: FontWeight::default(),
style: FontStyle::default(),
};
let font_size: Pixels = 14usize.into();
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
let display_map = cx.new(|cx| {
DisplayMap::new(
buffer,
font,
font_size,
None,
1,
1,
FoldPlaceholder::test(),
DiagnosticSeverity::Warning,
cx,
)
});
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
let markers = markers
.into_iter()
.map(|offset| MultiBufferOffset(offset).to_display_point(&snapshot))
.collect();
(snapshot, markers)
}
#[track_caller]
pub fn select_ranges(
editor: &mut Editor,
marked_text: &str,
window: &mut Window,
cx: &mut Context<Editor>,
) {
let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
assert_eq!(editor.text(cx), unmarked_text);
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.select_ranges(
text_ranges
.into_iter()
.map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
)
});
}
#[track_caller]
pub fn assert_text_with_selections(
editor: &mut Editor,
marked_text: &str,
cx: &mut Context<Editor>,
) {
let (unmarked_text, _text_ranges) = marked_text_ranges(marked_text, true);
assert_eq!(editor.text(cx), unmarked_text, "text doesn't match");
let actual = generate_marked_text(
&editor.text(cx),
&editor
.selections
.ranges::<MultiBufferOffset>(&editor.display_snapshot(cx))
.into_iter()
.map(|range| range.start.0..range.end.0)
.collect::<Vec<_>>(),
marked_text.contains("«"),
);
assert_eq!(actual, marked_text, "Selections don't match");
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn build_editor(
buffer: Entity<MultiBuffer>,
window: &mut Window,
cx: &mut Context<Editor>,
) -> Editor {
Editor::new(EditorMode::full(), buffer, None, window, cx)
}
pub(crate) fn build_editor_with_project(
project: Entity<Project>,
buffer: Entity<MultiBuffer>,
window: &mut Window,
cx: &mut Context<Editor>,
) -> Editor {
Editor::new(EditorMode::full(), buffer, Some(project), window, cx)
}
#[derive(Default)]
struct TestBlockContent(
HashMap<(EntityId, CustomBlockId), Rc<dyn Fn(&mut VisualTestContext) -> String>>,
);
impl gpui::Global for TestBlockContent {}
pub fn set_block_content_for_tests(
editor: &Entity<Editor>,
id: CustomBlockId,
cx: &mut App,
f: impl Fn(&mut VisualTestContext) -> String + 'static,
) {
cx.update_default_global::<TestBlockContent, _>(|bc, _| {
bc.0.insert((editor.entity_id(), id), Rc::new(f))
});
}
pub fn block_content_for_tests(
editor: &Entity<Editor>,
id: CustomBlockId,
cx: &mut VisualTestContext,
) -> Option<String> {
let f = cx.update(|_, cx| {
cx.default_global::<TestBlockContent>()
.0
.get(&(editor.entity_id(), id))
.cloned()
})?;
Some(f(cx))
}
pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestContext) -> String {
editor_content_with_blocks_and_width(editor, px(3000.), cx)
}
pub fn editor_content_with_blocks_and_width(
editor: &Entity<Editor>,
width: Pixels,
cx: &mut VisualTestContext,
) -> String {
editor_content_with_blocks_and_size(editor, size(width, px(3000.0)), cx)
}
pub fn editor_content_with_blocks_and_size(
editor: &Entity<Editor>,
draw_size: Size<Pixels>,
cx: &mut VisualTestContext,
) -> String {
cx.simulate_resize(draw_size);
cx.draw(gpui::Point::default(), draw_size, |_, _| {
editor.clone().into_any_element()
});
let (snapshot, mut lines, blocks) = editor.update_in(cx, |editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let text = editor.display_text(cx);
let lines = text.lines().map(|s| s.to_string()).collect::<Vec<String>>();
let blocks = snapshot
.blocks_in_range(DisplayRow(0)..snapshot.max_point().row())
.map(|(row, block)| (row, block.clone()))
.collect::<Vec<_>>();
(snapshot, lines, blocks)
});
for (row, block) in blocks {
match block {
Block::Custom(custom_block) => {
if let BlockPlacement::Near(x) = &custom_block.placement
&& snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot()))
{
continue;
};
let content = block_content_for_tests(editor, custom_block.id, cx)
.expect("block content not found");
// 2: "related info 1 for diagnostic 0"
if let Some(height) = custom_block.height {
if height == 0 {
lines[row.0 as usize - 1].push_str(" § ");
lines[row.0 as usize - 1].push_str(&content);
} else {
let block_lines = content.lines().collect::<Vec<_>>();
assert_eq!(block_lines.len(), height as usize);
lines[row.0 as usize].push_str("§ ");
lines[row.0 as usize].push_str(block_lines[0].trim_end());
for i in 1..height as usize {
if row.0 as usize + i >= lines.len() {
lines.push("".to_string());
};
lines[row.0 as usize + i].push_str("§ ");
lines[row.0 as usize + i].push_str(block_lines[i].trim_end());
}
}
}
}
Block::FoldedBuffer {
first_excerpt,
height,
} => {
while lines.len() <= row.0 as usize {
lines.push(String::new());
}
lines[row.0 as usize].push_str(&cx.update(|_, cx| {
format!(
"§ {}",
first_excerpt
.buffer
.file()
.map(|file| file.file_name(cx))
.unwrap_or("<no file>")
)
}));
for row in row.0 + 1..row.0 + height {
while lines.len() <= row as usize {
lines.push(String::new());
}
lines[row as usize].push_str("§ -----");
}
}
Block::ExcerptBoundary { height, .. } => {
for row in row.0..row.0 + height {
while lines.len() <= row as usize {
lines.push(String::new());
}
lines[row as usize].push_str("§ -----");
}
}
Block::BufferHeader { excerpt, height } => {
while lines.len() <= row.0 as usize {
lines.push(String::new());
}
lines[row.0 as usize].push_str(&cx.update(|_, cx| {
format!(
"§ {}",
excerpt
.buffer
.file()
.map(|file| file.file_name(cx))
.unwrap_or("<no file>")
)
}));
for row in row.0 + 1..row.0 + height {
while lines.len() <= row as usize {
lines.push(String::new());
}
lines[row as usize].push_str("§ -----");
}
}
Block::Spacer { height, .. } => {
for row in row.0..row.0 + height {
while lines.len() <= row as usize {
lines.push(String::new());
}
lines[row as usize].push_str("§ spacer");
}
}
}
}
lines.join("\n")
}