test.rs

  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, SelectionEffects, Size,
  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::{MultiBufferOffset, ToPoint};
 20use pretty_assertions::assert_eq;
 21use project::{Project, project_settings::DiagnosticSeverity};
 22use ui::{App, BorrowAppContext, IntoElement, px};
 23use util::test::{generate_marked_text, 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.
 48#[track_caller]
 49pub fn marked_display_snapshot(
 50    text: &str,
 51    cx: &mut gpui::App,
 52) -> (DisplaySnapshot, Vec<DisplayPoint>) {
 53    let (unmarked_text, markers) = marked_text_offsets(text);
 54
 55    let font = Font {
 56        family: ".ZedMono".into(),
 57        features: FontFeatures::default(),
 58        fallbacks: None,
 59        weight: FontWeight::default(),
 60        style: FontStyle::default(),
 61    };
 62    let font_size: Pixels = 14usize.into();
 63
 64    let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
 65    let display_map = cx.new(|cx| {
 66        DisplayMap::new(
 67            buffer,
 68            font,
 69            font_size,
 70            None,
 71            1,
 72            1,
 73            FoldPlaceholder::test(),
 74            DiagnosticSeverity::Warning,
 75            cx,
 76        )
 77    });
 78    let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
 79    let markers = markers
 80        .into_iter()
 81        .map(|offset| MultiBufferOffset(offset).to_display_point(&snapshot))
 82        .collect();
 83
 84    (snapshot, markers)
 85}
 86
 87#[track_caller]
 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(SelectionEffects::no_scroll(), window, cx, |s| {
 97        s.select_ranges(
 98            text_ranges
 99                .into_iter()
100                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
101        )
102    });
103}
104
105#[track_caller]
106pub fn assert_text_with_selections(
107    editor: &mut Editor,
108    marked_text: &str,
109    cx: &mut Context<Editor>,
110) {
111    let (unmarked_text, _text_ranges) = marked_text_ranges(marked_text, true);
112    assert_eq!(editor.text(cx), unmarked_text, "text doesn't match");
113    let actual = generate_marked_text(
114        &editor.text(cx),
115        &editor
116            .selections
117            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx))
118            .into_iter()
119            .map(|range| range.start.0..range.end.0)
120            .collect::<Vec<_>>(),
121        marked_text.contains("«"),
122    );
123    assert_eq!(actual, marked_text, "Selections don't match");
124}
125
126#[cfg(any(test, feature = "test-support"))]
127pub(crate) fn build_editor(
128    buffer: Entity<MultiBuffer>,
129    window: &mut Window,
130    cx: &mut Context<Editor>,
131) -> Editor {
132    Editor::new(EditorMode::full(), buffer, None, window, cx)
133}
134
135pub(crate) fn build_editor_with_project(
136    project: Entity<Project>,
137    buffer: Entity<MultiBuffer>,
138    window: &mut Window,
139    cx: &mut Context<Editor>,
140) -> Editor {
141    Editor::new(EditorMode::full(), buffer, Some(project), window, cx)
142}
143
144#[derive(Default)]
145struct TestBlockContent(
146    HashMap<(EntityId, CustomBlockId), Rc<dyn Fn(&mut VisualTestContext) -> String>>,
147);
148
149impl gpui::Global for TestBlockContent {}
150
151pub fn set_block_content_for_tests(
152    editor: &Entity<Editor>,
153    id: CustomBlockId,
154    cx: &mut App,
155    f: impl Fn(&mut VisualTestContext) -> String + 'static,
156) {
157    cx.update_default_global::<TestBlockContent, _>(|bc, _| {
158        bc.0.insert((editor.entity_id(), id), Rc::new(f))
159    });
160}
161
162pub fn block_content_for_tests(
163    editor: &Entity<Editor>,
164    id: CustomBlockId,
165    cx: &mut VisualTestContext,
166) -> Option<String> {
167    let f = cx.update(|_, cx| {
168        cx.default_global::<TestBlockContent>()
169            .0
170            .get(&(editor.entity_id(), id))
171            .cloned()
172    })?;
173    Some(f(cx))
174}
175
176pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestContext) -> String {
177    editor_content_with_blocks_and_width(editor, px(3000.), cx)
178}
179
180pub fn editor_content_with_blocks_and_width(
181    editor: &Entity<Editor>,
182    width: Pixels,
183    cx: &mut VisualTestContext,
184) -> String {
185    editor_content_with_blocks_and_size(editor, size(width, px(3000.0)), cx)
186}
187
188pub fn editor_content_with_blocks_and_size(
189    editor: &Entity<Editor>,
190    draw_size: Size<Pixels>,
191    cx: &mut VisualTestContext,
192) -> String {
193    cx.simulate_resize(draw_size);
194    cx.draw(gpui::Point::default(), draw_size, |_, _| {
195        editor.clone().into_any_element()
196    });
197    let (snapshot, mut lines, blocks) = editor.update_in(cx, |editor, window, cx| {
198        let snapshot = editor.snapshot(window, cx);
199        let text = editor.display_text(cx);
200        let lines = text.lines().map(|s| s.to_string()).collect::<Vec<String>>();
201        let blocks = snapshot
202            .blocks_in_range(DisplayRow(0)..snapshot.max_point().row())
203            .map(|(row, block)| (row, block.clone()))
204            .collect::<Vec<_>>();
205        (snapshot, lines, blocks)
206    });
207    for (row, block) in blocks {
208        match block {
209            Block::Custom(custom_block) => {
210                if let BlockPlacement::Near(x) = &custom_block.placement
211                    && snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot()))
212                {
213                    continue;
214                };
215                let content = block_content_for_tests(editor, custom_block.id, cx)
216                    .expect("block content not found");
217                // 2: "related info 1 for diagnostic 0"
218                if let Some(height) = custom_block.height {
219                    if height == 0 {
220                        lines[row.0 as usize - 1].push_str(" § ");
221                        lines[row.0 as usize - 1].push_str(&content);
222                    } else {
223                        let block_lines = content.lines().collect::<Vec<_>>();
224                        assert_eq!(block_lines.len(), height as usize);
225                        lines[row.0 as usize].push_str("§ ");
226                        lines[row.0 as usize].push_str(block_lines[0].trim_end());
227                        for i in 1..height as usize {
228                            if row.0 as usize + i >= lines.len() {
229                                lines.push("".to_string());
230                            };
231                            lines[row.0 as usize + i].push_str("§ ");
232                            lines[row.0 as usize + i].push_str(block_lines[i].trim_end());
233                        }
234                    }
235                }
236            }
237            Block::FoldedBuffer {
238                first_excerpt,
239                height,
240            } => {
241                while lines.len() <= row.0 as usize {
242                    lines.push(String::new());
243                }
244                lines[row.0 as usize].push_str(&cx.update(|_, cx| {
245                    format!(
246                        "§ {}",
247                        first_excerpt
248                            .buffer
249                            .file()
250                            .map(|file| file.file_name(cx))
251                            .unwrap_or("<no file>")
252                    )
253                }));
254                for row in row.0 + 1..row.0 + height {
255                    while lines.len() <= row as usize {
256                        lines.push(String::new());
257                    }
258                    lines[row as usize].push_str("§ -----");
259                }
260            }
261            Block::ExcerptBoundary { height, .. } => {
262                for row in row.0..row.0 + height {
263                    while lines.len() <= row as usize {
264                        lines.push(String::new());
265                    }
266                    lines[row as usize].push_str("§ -----");
267                }
268            }
269            Block::BufferHeader { excerpt, height } => {
270                while lines.len() <= row.0 as usize {
271                    lines.push(String::new());
272                }
273                lines[row.0 as usize].push_str(&cx.update(|_, cx| {
274                    format!(
275                        "§ {}",
276                        excerpt
277                            .buffer
278                            .file()
279                            .map(|file| file.file_name(cx))
280                            .unwrap_or("<no file>")
281                    )
282                }));
283                for row in row.0 + 1..row.0 + height {
284                    while lines.len() <= row as usize {
285                        lines.push(String::new());
286                    }
287                    lines[row as usize].push_str("§ -----");
288                }
289            }
290            Block::Spacer { height, .. } => {
291                for row in row.0..row.0 + height {
292                    while lines.len() <= row as usize {
293                        lines.push(String::new());
294                    }
295                    lines[row as usize].push_str("§ spacer");
296                }
297            }
298        }
299    }
300    lines.join("\n")
301}