1use std::{
2 any::TypeId,
3 ops::{Deref, DerefMut, Range},
4 sync::Arc,
5};
6
7use anyhow::Result;
8use futures::{Future, StreamExt};
9use indoc::indoc;
10
11use gpui::{
12 json, keymap::Keystroke, AppContext, ModelContext, ModelHandle, ViewContext, ViewHandle,
13};
14use language::{
15 point_to_lsp, Buffer, BufferSnapshot, FakeLspAdapter, Language, LanguageConfig, Selection,
16};
17use lsp::{notification, request};
18use project::Project;
19use settings::Settings;
20use util::{
21 assert_set_eq, set_eq,
22 test::{generate_marked_text, marked_text, parse_marked_text},
23};
24use workspace::{pane, AppState, Workspace, WorkspaceHandle};
25
26use crate::{
27 display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
28 multi_buffer::ToPointUtf16,
29 AnchorRangeExt, Autoscroll, DisplayPoint, Editor, EditorMode, MultiBuffer, ToPoint,
30};
31
32#[cfg(test)]
33#[ctor::ctor]
34fn init_logger() {
35 if std::env::var("RUST_LOG").is_ok() {
36 env_logger::init();
37 }
38}
39
40// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
41pub fn marked_display_snapshot(
42 text: &str,
43 cx: &mut gpui::MutableAppContext,
44) -> (DisplaySnapshot, Vec<DisplayPoint>) {
45 let (unmarked_text, markers) = marked_text(text);
46
47 let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
48 let font_id = cx
49 .font_cache()
50 .select_font(family_id, &Default::default())
51 .unwrap();
52 let font_size = 14.0;
53
54 let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
55 let display_map =
56 cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
57 let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
58 let markers = markers
59 .into_iter()
60 .map(|offset| offset.to_display_point(&snapshot))
61 .collect();
62
63 (snapshot, markers)
64}
65
66pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
67 let (umarked_text, text_ranges) = parse_marked_text(marked_text, true).unwrap();
68 assert_eq!(editor.text(cx), umarked_text);
69 editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
70}
71
72pub fn assert_text_with_selections(
73 editor: &mut Editor,
74 marked_text: &str,
75 cx: &mut ViewContext<Editor>,
76) {
77 let (unmarked_text, text_ranges) = parse_marked_text(marked_text, true).unwrap();
78 assert_eq!(editor.text(cx), unmarked_text);
79 assert_eq!(editor.selections.ranges(cx), text_ranges);
80}
81
82pub(crate) fn build_editor(
83 buffer: ModelHandle<MultiBuffer>,
84 cx: &mut ViewContext<Editor>,
85) -> Editor {
86 Editor::new(EditorMode::Full, buffer, None, None, cx)
87}
88
89pub struct EditorTestContext<'a> {
90 pub cx: &'a mut gpui::TestAppContext,
91 pub window_id: usize,
92 pub editor: ViewHandle<Editor>,
93}
94
95impl<'a> EditorTestContext<'a> {
96 pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
97 let (window_id, editor) = cx.update(|cx| {
98 cx.set_global(Settings::test(cx));
99 crate::init(cx);
100
101 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
102 build_editor(MultiBuffer::build_simple("", cx), cx)
103 });
104
105 editor.update(cx, |_, cx| cx.focus_self());
106
107 (window_id, editor)
108 });
109
110 Self {
111 cx,
112 window_id,
113 editor,
114 }
115 }
116
117 pub fn condition(
118 &self,
119 predicate: impl FnMut(&Editor, &AppContext) -> bool,
120 ) -> impl Future<Output = ()> {
121 self.editor.condition(self.cx, predicate)
122 }
123
124 pub fn editor<F, T>(&self, read: F) -> T
125 where
126 F: FnOnce(&Editor, &AppContext) -> T,
127 {
128 self.editor.read_with(self.cx, read)
129 }
130
131 pub fn update_editor<F, T>(&mut self, update: F) -> T
132 where
133 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
134 {
135 self.editor.update(self.cx, update)
136 }
137
138 pub fn multibuffer<F, T>(&self, read: F) -> T
139 where
140 F: FnOnce(&MultiBuffer, &AppContext) -> T,
141 {
142 self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
143 }
144
145 pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
146 where
147 F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
148 {
149 self.update_editor(|editor, cx| editor.buffer().update(cx, update))
150 }
151
152 pub fn buffer_text(&self) -> String {
153 self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
154 }
155
156 pub fn buffer<F, T>(&self, read: F) -> T
157 where
158 F: FnOnce(&Buffer, &AppContext) -> T,
159 {
160 self.multibuffer(|multibuffer, cx| {
161 let buffer = multibuffer.as_singleton().unwrap().read(cx);
162 read(buffer, cx)
163 })
164 }
165
166 pub fn update_buffer<F, T>(&mut self, update: F) -> T
167 where
168 F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
169 {
170 self.update_multibuffer(|multibuffer, cx| {
171 let buffer = multibuffer.as_singleton().unwrap();
172 buffer.update(cx, update)
173 })
174 }
175
176 pub fn buffer_snapshot(&self) -> BufferSnapshot {
177 self.buffer(|buffer, _| buffer.snapshot())
178 }
179
180 pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
181 let keystroke = Keystroke::parse(keystroke_text).unwrap();
182 self.cx.dispatch_keystroke(self.window_id, keystroke, false);
183 }
184
185 pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
186 for keystroke_text in keystroke_texts.into_iter() {
187 self.simulate_keystroke(keystroke_text);
188 }
189 }
190
191 pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
192 let (unmarked_text, ranges) = parse_marked_text(marked_text, false).unwrap();
193 assert_eq!(self.buffer_text(), unmarked_text);
194 ranges
195 }
196
197 pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
198 let ranges = self.ranges(marked_text);
199 let snapshot = self
200 .editor
201 .update(self.cx, |editor, cx| editor.snapshot(cx));
202 ranges[0].start.to_display_point(&snapshot)
203 }
204
205 // Returns anchors for the current buffer using `«` and `»`
206 pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
207 let ranges = self.ranges(marked_text);
208 let snapshot = self.buffer_snapshot();
209 snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
210 }
211
212 pub fn set_state(&mut self, marked_text: &str) {
213 let (unmarked_text, selection_ranges) = parse_marked_text(marked_text, true).unwrap();
214 self.editor.update(self.cx, |editor, cx| {
215 editor.set_text(unmarked_text, cx);
216 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
217 s.select_ranges(selection_ranges)
218 })
219 })
220 }
221
222 pub fn assert_editor_state(&mut self, marked_text: &str) {
223 let (unmarked_text, expected_selections) = parse_marked_text(marked_text, true).unwrap();
224 let buffer_text = self.buffer_text();
225 assert_eq!(
226 buffer_text, unmarked_text,
227 "Unmarked text doesn't match buffer text"
228 );
229 self.assert_selections(expected_selections, marked_text.to_string())
230 }
231
232 pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
233 let expected_ranges = self.ranges(marked_text);
234 let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
235 let snapshot = editor.snapshot(cx);
236 editor
237 .background_highlights
238 .get(&TypeId::of::<Tag>())
239 .map(|h| h.1.clone())
240 .unwrap_or_default()
241 .into_iter()
242 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
243 .collect()
244 });
245 assert_set_eq!(actual_ranges, expected_ranges);
246 }
247
248 pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
249 let expected_ranges = self.ranges(marked_text);
250 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
251 let actual_ranges: Vec<Range<usize>> = snapshot
252 .highlight_ranges::<Tag>()
253 .map(|ranges| ranges.as_ref().clone().1)
254 .unwrap_or_default()
255 .into_iter()
256 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
257 .collect();
258 assert_set_eq!(actual_ranges, expected_ranges);
259 }
260
261 pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
262 let expected_selections = expected_selections
263 .into_iter()
264 .map(|s| s.range())
265 .collect::<Vec<_>>();
266 let expected_marked_text =
267 generate_marked_text(&self.buffer_text(), &expected_selections, true);
268 self.assert_selections(expected_selections, expected_marked_text)
269 }
270
271 fn assert_selections(
272 &mut self,
273 expected_selections: Vec<Range<usize>>,
274 expected_marked_text: String,
275 ) {
276 let actual_selections = self
277 .editor
278 .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
279 .into_iter()
280 .map(|s| s.range())
281 .collect::<Vec<_>>();
282 let actual_marked_text =
283 generate_marked_text(&self.buffer_text(), &actual_selections, true);
284 if expected_selections != actual_selections {
285 panic!(
286 indoc! {"
287 Editor has unexpected selections.
288 Expected selections:
289 {}
290 Actual selections:
291 {}",
292 },
293 expected_marked_text, actual_marked_text,
294 );
295 }
296 }
297}
298
299impl<'a> Deref for EditorTestContext<'a> {
300 type Target = gpui::TestAppContext;
301
302 fn deref(&self) -> &Self::Target {
303 self.cx
304 }
305}
306
307impl<'a> DerefMut for EditorTestContext<'a> {
308 fn deref_mut(&mut self) -> &mut Self::Target {
309 &mut self.cx
310 }
311}
312
313pub struct EditorLspTestContext<'a> {
314 pub cx: EditorTestContext<'a>,
315 pub lsp: lsp::FakeLanguageServer,
316 pub workspace: ViewHandle<Workspace>,
317 pub buffer_lsp_url: lsp::Url,
318}
319
320impl<'a> EditorLspTestContext<'a> {
321 pub async fn new(
322 mut language: Language,
323 capabilities: lsp::ServerCapabilities,
324 cx: &'a mut gpui::TestAppContext,
325 ) -> EditorLspTestContext<'a> {
326 use json::json;
327
328 cx.update(|cx| {
329 crate::init(cx);
330 pane::init(cx);
331 });
332
333 let params = cx.update(AppState::test);
334
335 let file_name = format!(
336 "file.{}",
337 language
338 .path_suffixes()
339 .first()
340 .unwrap_or(&"txt".to_string())
341 );
342
343 let mut fake_servers = language
344 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
345 capabilities,
346 ..Default::default()
347 }))
348 .await;
349
350 let project = Project::test(params.fs.clone(), [], cx).await;
351 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
352
353 params
354 .fs
355 .as_fake()
356 .insert_tree("/root", json!({ "dir": { file_name: "" }}))
357 .await;
358
359 let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
360 project
361 .update(cx, |project, cx| {
362 project.find_or_create_local_worktree("/root", true, cx)
363 })
364 .await
365 .unwrap();
366 cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
367 .await;
368
369 let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
370 let item = workspace
371 .update(cx, |workspace, cx| workspace.open_path(file, true, cx))
372 .await
373 .expect("Could not open test file");
374
375 let editor = cx.update(|cx| {
376 item.act_as::<Editor>(cx)
377 .expect("Opened test file wasn't an editor")
378 });
379 editor.update(cx, |_, cx| cx.focus_self());
380
381 let lsp = fake_servers.next().await.unwrap();
382
383 Self {
384 cx: EditorTestContext {
385 cx,
386 window_id,
387 editor,
388 },
389 lsp,
390 workspace,
391 buffer_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
392 }
393 }
394
395 pub async fn new_rust(
396 capabilities: lsp::ServerCapabilities,
397 cx: &'a mut gpui::TestAppContext,
398 ) -> EditorLspTestContext<'a> {
399 let language = Language::new(
400 LanguageConfig {
401 name: "Rust".into(),
402 path_suffixes: vec!["rs".to_string()],
403 ..Default::default()
404 },
405 Some(tree_sitter_rust::language()),
406 );
407
408 Self::new(language, capabilities, cx).await
409 }
410
411 // Constructs lsp range using a marked string with '[', ']' range delimiters
412 pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
413 let ranges = self.ranges(marked_text);
414 self.to_lsp_range(ranges[0].clone())
415 }
416
417 pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
418 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
419 let start_point = range.start.to_point(&snapshot.buffer_snapshot);
420 let end_point = range.end.to_point(&snapshot.buffer_snapshot);
421
422 self.editor(|editor, cx| {
423 let buffer = editor.buffer().read(cx);
424 let start = point_to_lsp(
425 buffer
426 .point_to_buffer_offset(start_point, cx)
427 .unwrap()
428 .1
429 .to_point_utf16(&buffer.read(cx)),
430 );
431 let end = point_to_lsp(
432 buffer
433 .point_to_buffer_offset(end_point, cx)
434 .unwrap()
435 .1
436 .to_point_utf16(&buffer.read(cx)),
437 );
438
439 lsp::Range { start, end }
440 })
441 }
442
443 pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
444 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
445 let point = offset.to_point(&snapshot.buffer_snapshot);
446
447 self.editor(|editor, cx| {
448 let buffer = editor.buffer().read(cx);
449 point_to_lsp(
450 buffer
451 .point_to_buffer_offset(point, cx)
452 .unwrap()
453 .1
454 .to_point_utf16(&buffer.read(cx)),
455 )
456 })
457 }
458
459 pub fn update_workspace<F, T>(&mut self, update: F) -> T
460 where
461 F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
462 {
463 self.workspace.update(self.cx.cx, update)
464 }
465
466 pub fn handle_request<T, F, Fut>(
467 &self,
468 mut handler: F,
469 ) -> futures::channel::mpsc::UnboundedReceiver<()>
470 where
471 T: 'static + request::Request,
472 T::Params: 'static + Send,
473 F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
474 Fut: 'static + Send + Future<Output = Result<T::Result>>,
475 {
476 let url = self.buffer_lsp_url.clone();
477 self.lsp.handle_request::<T, _, _>(move |params, cx| {
478 let url = url.clone();
479 handler(url, params, cx)
480 })
481 }
482
483 pub fn notify<T: notification::Notification>(&self, params: T::Params) {
484 self.lsp.notify::<T>(params);
485 }
486}
487
488impl<'a> Deref for EditorLspTestContext<'a> {
489 type Target = EditorTestContext<'a>;
490
491 fn deref(&self) -> &Self::Target {
492 &self.cx
493 }
494}
495
496impl<'a> DerefMut for EditorLspTestContext<'a> {
497 fn deref_mut(&mut self) -> &mut Self::Target {
498 &mut self.cx
499 }
500}