1pub mod editor_lsp_test_context;
2pub mod editor_test_context;
3
4use std::{rc::Rc, sync::LazyLock};
5
6pub use crate::rust_analyzer_ext::expand_macro_recursively;
7use crate::{
8 DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer,
9 display_map::{
10 Block, BlockPlacement, CustomBlockId, DisplayMap, DisplayRow, DisplaySnapshot,
11 ToDisplayPoint,
12 },
13};
14use collections::HashMap;
15use gpui::{
16 AppContext as _, Context, Entity, EntityId, Font, FontFeatures, FontStyle, FontWeight, Pixels,
17 VisualTestContext, Window, font, size,
18};
19use multi_buffer::ToPoint;
20use pretty_assertions::assert_eq;
21use project::{Project, project_settings::DiagnosticSeverity};
22use ui::{App, BorrowAppContext, px};
23use util::test::{marked_text_offsets, marked_text_ranges};
24
25#[cfg(test)]
26#[ctor::ctor]
27fn init_logger() {
28 zlog::init_test();
29}
30
31pub fn test_font() -> Font {
32 static TEST_FONT: LazyLock<Font> = LazyLock::new(|| {
33 #[cfg(not(target_os = "windows"))]
34 {
35 font("Helvetica")
36 }
37
38 #[cfg(target_os = "windows")]
39 {
40 font("Courier New")
41 }
42 });
43
44 TEST_FONT.clone()
45}
46
47// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
48pub fn marked_display_snapshot(
49 text: &str,
50 cx: &mut gpui::App,
51) -> (DisplaySnapshot, Vec<DisplayPoint>) {
52 let (unmarked_text, markers) = marked_text_offsets(text);
53
54 let font = Font {
55 family: "Zed Plex Mono".into(),
56 features: FontFeatures::default(),
57 fallbacks: None,
58 weight: FontWeight::default(),
59 style: FontStyle::default(),
60 };
61 let font_size: Pixels = 14usize.into();
62
63 let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
64 let display_map = cx.new(|cx| {
65 DisplayMap::new(
66 buffer,
67 font,
68 font_size,
69 None,
70 1,
71 1,
72 FoldPlaceholder::test(),
73 DiagnosticSeverity::Warning,
74 cx,
75 )
76 });
77 let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
78 let markers = markers
79 .into_iter()
80 .map(|offset| offset.to_display_point(&snapshot))
81 .collect();
82
83 (snapshot, markers)
84}
85
86pub fn select_ranges(
87 editor: &mut Editor,
88 marked_text: &str,
89 window: &mut Window,
90 cx: &mut Context<Editor>,
91) {
92 let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
93 assert_eq!(editor.text(cx), unmarked_text);
94 editor.change_selections(None, window, cx, |s| s.select_ranges(text_ranges));
95}
96
97#[track_caller]
98pub fn assert_text_with_selections(
99 editor: &mut Editor,
100 marked_text: &str,
101 cx: &mut Context<Editor>,
102) {
103 let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
104 assert_eq!(editor.text(cx), unmarked_text, "text doesn't match");
105 assert_eq!(
106 editor.selections.ranges(cx),
107 text_ranges,
108 "selections don't match",
109 );
110}
111
112// RA thinks this is dead code even though it is used in a whole lot of tests
113#[allow(dead_code)]
114#[cfg(any(test, feature = "test-support"))]
115pub(crate) fn build_editor(
116 buffer: Entity<MultiBuffer>,
117 window: &mut Window,
118 cx: &mut Context<Editor>,
119) -> Editor {
120 Editor::new(EditorMode::full(), buffer, None, window, cx)
121}
122
123pub(crate) fn build_editor_with_project(
124 project: Entity<Project>,
125 buffer: Entity<MultiBuffer>,
126 window: &mut Window,
127 cx: &mut Context<Editor>,
128) -> Editor {
129 Editor::new(EditorMode::full(), buffer, Some(project), window, cx)
130}
131
132#[derive(Default)]
133struct TestBlockContent(
134 HashMap<(EntityId, CustomBlockId), Rc<dyn Fn(&mut VisualTestContext) -> String>>,
135);
136
137impl gpui::Global for TestBlockContent {}
138
139pub fn set_block_content_for_tests(
140 editor: &Entity<Editor>,
141 id: CustomBlockId,
142 cx: &mut App,
143 f: impl Fn(&mut VisualTestContext) -> String + 'static,
144) {
145 cx.update_default_global::<TestBlockContent, _>(|bc, _| {
146 bc.0.insert((editor.entity_id(), id), Rc::new(f))
147 });
148}
149
150pub fn block_content_for_tests(
151 editor: &Entity<Editor>,
152 id: CustomBlockId,
153 cx: &mut VisualTestContext,
154) -> Option<String> {
155 let f = cx.update(|_, cx| {
156 cx.default_global::<TestBlockContent>()
157 .0
158 .get(&(editor.entity_id(), id))
159 .cloned()
160 })?;
161 Some(f(cx))
162}
163
164pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestContext) -> String {
165 cx.draw(
166 gpui::Point::default(),
167 size(px(3000.0), px(3000.0)),
168 |_, _| editor.clone(),
169 );
170 let (snapshot, mut lines, blocks) = editor.update_in(cx, |editor, window, cx| {
171 let snapshot = editor.snapshot(window, cx);
172 let text = editor.display_text(cx);
173 let lines = text.lines().map(|s| s.to_string()).collect::<Vec<String>>();
174 let blocks = snapshot
175 .blocks_in_range(DisplayRow(0)..snapshot.max_point().row())
176 .map(|(row, block)| (row, block.clone()))
177 .collect::<Vec<_>>();
178 (snapshot, lines, blocks)
179 });
180 for (row, block) in blocks {
181 match block {
182 Block::Custom(custom_block) => {
183 if let BlockPlacement::Near(x) = &custom_block.placement {
184 if snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot)) {
185 continue;
186 }
187 };
188 let content = block_content_for_tests(&editor, custom_block.id, cx)
189 .expect("block content not found");
190 // 2: "related info 1 for diagnostic 0"
191 if let Some(height) = custom_block.height {
192 if height == 0 {
193 lines[row.0 as usize - 1].push_str(" § ");
194 lines[row.0 as usize - 1].push_str(&content);
195 } else {
196 let block_lines = content.lines().collect::<Vec<_>>();
197 assert_eq!(block_lines.len(), height as usize);
198 lines[row.0 as usize].push_str("§ ");
199 lines[row.0 as usize].push_str(block_lines[0].trim_end());
200 for i in 1..height as usize {
201 if row.0 as usize + i >= lines.len() {
202 lines.push("".to_string());
203 };
204 lines[row.0 as usize + i].push_str("§ ");
205 lines[row.0 as usize + i].push_str(block_lines[i].trim_end());
206 }
207 }
208 }
209 }
210 Block::FoldedBuffer {
211 first_excerpt,
212 height,
213 } => {
214 lines[row.0 as usize].push_str(&cx.update(|_, cx| {
215 format!(
216 "§ {}",
217 first_excerpt
218 .buffer
219 .file()
220 .unwrap()
221 .file_name(cx)
222 .to_string_lossy()
223 )
224 }));
225 for row in row.0 + 1..row.0 + height {
226 lines[row as usize].push_str("§ -----");
227 }
228 }
229 Block::ExcerptBoundary {
230 excerpt,
231 height,
232 starts_new_buffer,
233 } => {
234 if starts_new_buffer {
235 lines[row.0 as usize].push_str(&cx.update(|_, cx| {
236 format!(
237 "§ {}",
238 excerpt
239 .buffer
240 .file()
241 .unwrap()
242 .file_name(cx)
243 .to_string_lossy()
244 )
245 }));
246 } else {
247 lines[row.0 as usize].push_str("§ -----")
248 }
249 for row in row.0 + 1..row.0 + height {
250 lines[row as usize].push_str("§ -----");
251 }
252 }
253 }
254 }
255 lines.join("\n")
256}