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