1// use crate::{Event, *};
2// use fs::FakeFs;
3// use futures::{future, StreamExt};
4// use gpui::AppContext;
5// use language::{
6// language_settings::{AllLanguageSettings, LanguageSettingsContent},
7// tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
8// LineEnding, OffsetRangeExt, Point, ToPoint,
9// };
10// use lsp::Url;
11// use parking_lot::Mutex;
12// use pretty_assertions::assert_eq;
13// use serde_json::json;
14// use std::{os, task::Poll};
15// use unindent::Unindent as _;
16// use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
17
18// #[gpui::test]
19// async fn test_block_via_channel(cx: &mut gpui2::TestAppContext) {
20// cx.executor().allow_parking();
21// }
22
23// #[cfg(test)]
24// #[ctor::ctor]
25// fn init_logger() {
26// if std::env::var("RUST_LOG").is_ok() {
27// env_logger::init();
28// }
29// }
30
31// #[gpui::test]
32// async fn test_symlinks(cx: &mut gpui::TestAppContext) {
33// init_test(cx);
34// cx.foreground().allow_parking();
35
36// let dir = temp_tree(json!({
37// "root": {
38// "apple": "",
39// "banana": {
40// "carrot": {
41// "date": "",
42// "endive": "",
43// }
44// },
45// "fennel": {
46// "grape": "",
47// }
48// }
49// }));
50
51// let root_link_path = dir.path().join("root_link");
52// unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
53// unix::fs::symlink(
54// &dir.path().join("root/fennel"),
55// &dir.path().join("root/finnochio"),
56// )
57// .unwrap();
58
59// let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
60// project.read_with(cx, |project, cx| {
61// let tree = project.worktrees(cx).next().unwrap().read(cx);
62// assert_eq!(tree.file_count(), 5);
63// assert_eq!(
64// tree.inode_for_path("fennel/grape"),
65// tree.inode_for_path("finnochio/grape")
66// );
67// });
68// }
69
70// #[gpui::test]
71// async fn test_managing_project_specific_settings(
72// deterministic: Arc<Deterministic>,
73// cx: &mut gpui::TestAppContext,
74// ) {
75// init_test(cx);
76
77// let fs = FakeFs::new(cx.background());
78// fs.insert_tree(
79// "/the-root",
80// json!({
81// ".zed": {
82// "settings.json": r#"{ "tab_size": 8 }"#
83// },
84// "a": {
85// "a.rs": "fn a() {\n A\n}"
86// },
87// "b": {
88// ".zed": {
89// "settings.json": r#"{ "tab_size": 2 }"#
90// },
91// "b.rs": "fn b() {\n B\n}"
92// }
93// }),
94// )
95// .await;
96
97// let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
98// let worktree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
99
100// deterministic.run_until_parked();
101// cx.read(|cx| {
102// let tree = worktree.read(cx);
103
104// let settings_a = language_settings(
105// None,
106// Some(
107// &(File::for_entry(
108// tree.entry_for_path("a/a.rs").unwrap().clone(),
109// worktree.clone(),
110// ) as _),
111// ),
112// cx,
113// );
114// let settings_b = language_settings(
115// None,
116// Some(
117// &(File::for_entry(
118// tree.entry_for_path("b/b.rs").unwrap().clone(),
119// worktree.clone(),
120// ) as _),
121// ),
122// cx,
123// );
124
125// assert_eq!(settings_a.tab_size.get(), 8);
126// assert_eq!(settings_b.tab_size.get(), 2);
127// });
128// }
129
130// #[gpui::test]
131// async fn test_managing_language_servers(
132// deterministic: Arc<Deterministic>,
133// cx: &mut gpui::TestAppContext,
134// ) {
135// init_test(cx);
136
137// let mut rust_language = Language::new(
138// LanguageConfig {
139// name: "Rust".into(),
140// path_suffixes: vec!["rs".to_string()],
141// ..Default::default()
142// },
143// Some(tree_sitter_rust::language()),
144// );
145// let mut json_language = Language::new(
146// LanguageConfig {
147// name: "JSON".into(),
148// path_suffixes: vec!["json".to_string()],
149// ..Default::default()
150// },
151// None,
152// );
153// let mut fake_rust_servers = rust_language
154// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
155// name: "the-rust-language-server",
156// capabilities: lsp::ServerCapabilities {
157// completion_provider: Some(lsp::CompletionOptions {
158// trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
159// ..Default::default()
160// }),
161// ..Default::default()
162// },
163// ..Default::default()
164// }))
165// .await;
166// let mut fake_json_servers = json_language
167// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
168// name: "the-json-language-server",
169// capabilities: lsp::ServerCapabilities {
170// completion_provider: Some(lsp::CompletionOptions {
171// trigger_characters: Some(vec![":".to_string()]),
172// ..Default::default()
173// }),
174// ..Default::default()
175// },
176// ..Default::default()
177// }))
178// .await;
179
180// let fs = FakeFs::new(cx.background());
181// fs.insert_tree(
182// "/the-root",
183// json!({
184// "test.rs": "const A: i32 = 1;",
185// "test2.rs": "",
186// "Cargo.toml": "a = 1",
187// "package.json": "{\"a\": 1}",
188// }),
189// )
190// .await;
191
192// let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
193
194// // Open a buffer without an associated language server.
195// let toml_buffer = project
196// .update(cx, |project, cx| {
197// project.open_local_buffer("/the-root/Cargo.toml", cx)
198// })
199// .await
200// .unwrap();
201
202// // Open a buffer with an associated language server before the language for it has been loaded.
203// let rust_buffer = project
204// .update(cx, |project, cx| {
205// project.open_local_buffer("/the-root/test.rs", cx)
206// })
207// .await
208// .unwrap();
209// rust_buffer.read_with(cx, |buffer, _| {
210// assert_eq!(buffer.language().map(|l| l.name()), None);
211// });
212
213// // Now we add the languages to the project, and ensure they get assigned to all
214// // the relevant open buffers.
215// project.update(cx, |project, _| {
216// project.languages.add(Arc::new(json_language));
217// project.languages.add(Arc::new(rust_language));
218// });
219// deterministic.run_until_parked();
220// rust_buffer.read_with(cx, |buffer, _| {
221// assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into()));
222// });
223
224// // A server is started up, and it is notified about Rust files.
225// let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
226// assert_eq!(
227// fake_rust_server
228// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
229// .await
230// .text_document,
231// lsp2::TextDocumentItem {
232// uri: lsp2::Url::from_file_path("/the-root/test.rs").unwrap(),
233// version: 0,
234// text: "const A: i32 = 1;".to_string(),
235// language_id: Default::default()
236// }
237// );
238
239// // The buffer is configured based on the language server's capabilities.
240// rust_buffer.read_with(cx, |buffer, _| {
241// assert_eq!(
242// buffer.completion_triggers(),
243// &[".".to_string(), "::".to_string()]
244// );
245// });
246// toml_buffer.read_with(cx, |buffer, _| {
247// assert!(buffer.completion_triggers().is_empty());
248// });
249
250// // Edit a buffer. The changes are reported to the language server.
251// rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx));
252// assert_eq!(
253// fake_rust_server
254// .receive_notification::<lsp2::notification::DidChangeTextDocument>()
255// .await
256// .text_document,
257// lsp2::VersionedTextDocumentIdentifier::new(
258// lsp2::Url::from_file_path("/the-root/test.rs").unwrap(),
259// 1
260// )
261// );
262
263// // Open a third buffer with a different associated language server.
264// let json_buffer = project
265// .update(cx, |project, cx| {
266// project.open_local_buffer("/the-root/package.json", cx)
267// })
268// .await
269// .unwrap();
270
271// // A json language server is started up and is only notified about the json buffer.
272// let mut fake_json_server = fake_json_servers.next().await.unwrap();
273// assert_eq!(
274// fake_json_server
275// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
276// .await
277// .text_document,
278// lsp2::TextDocumentItem {
279// uri: lsp2::Url::from_file_path("/the-root/package.json").unwrap(),
280// version: 0,
281// text: "{\"a\": 1}".to_string(),
282// language_id: Default::default()
283// }
284// );
285
286// // This buffer is configured based on the second language server's
287// // capabilities.
288// json_buffer.read_with(cx, |buffer, _| {
289// assert_eq!(buffer.completion_triggers(), &[":".to_string()]);
290// });
291
292// // When opening another buffer whose language server is already running,
293// // it is also configured based on the existing language server's capabilities.
294// let rust_buffer2 = project
295// .update(cx, |project, cx| {
296// project.open_local_buffer("/the-root/test2.rs", cx)
297// })
298// .await
299// .unwrap();
300// rust_buffer2.read_with(cx, |buffer, _| {
301// assert_eq!(
302// buffer.completion_triggers(),
303// &[".".to_string(), "::".to_string()]
304// );
305// });
306
307// // Changes are reported only to servers matching the buffer's language.
308// toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], None, cx));
309// rust_buffer2.update(cx, |buffer, cx| {
310// buffer.edit([(0..0, "let x = 1;")], None, cx)
311// });
312// assert_eq!(
313// fake_rust_server
314// .receive_notification::<lsp2::notification::DidChangeTextDocument>()
315// .await
316// .text_document,
317// lsp2::VersionedTextDocumentIdentifier::new(
318// lsp2::Url::from_file_path("/the-root/test2.rs").unwrap(),
319// 1
320// )
321// );
322
323// // Save notifications are reported to all servers.
324// project
325// .update(cx, |project, cx| project.save_buffer(toml_buffer, cx))
326// .await
327// .unwrap();
328// assert_eq!(
329// fake_rust_server
330// .receive_notification::<lsp2::notification::DidSaveTextDocument>()
331// .await
332// .text_document,
333// lsp2::TextDocumentIdentifier::new(
334// lsp2::Url::from_file_path("/the-root/Cargo.toml").unwrap()
335// )
336// );
337// assert_eq!(
338// fake_json_server
339// .receive_notification::<lsp2::notification::DidSaveTextDocument>()
340// .await
341// .text_document,
342// lsp2::TextDocumentIdentifier::new(
343// lsp2::Url::from_file_path("/the-root/Cargo.toml").unwrap()
344// )
345// );
346
347// // Renames are reported only to servers matching the buffer's language.
348// fs.rename(
349// Path::new("/the-root/test2.rs"),
350// Path::new("/the-root/test3.rs"),
351// Default::default(),
352// )
353// .await
354// .unwrap();
355// assert_eq!(
356// fake_rust_server
357// .receive_notification::<lsp2::notification::DidCloseTextDocument>()
358// .await
359// .text_document,
360// lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path("/the-root/test2.rs").unwrap()),
361// );
362// assert_eq!(
363// fake_rust_server
364// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
365// .await
366// .text_document,
367// lsp2::TextDocumentItem {
368// uri: lsp2::Url::from_file_path("/the-root/test3.rs").unwrap(),
369// version: 0,
370// text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
371// language_id: Default::default()
372// },
373// );
374
375// rust_buffer2.update(cx, |buffer, cx| {
376// buffer.update_diagnostics(
377// LanguageServerId(0),
378// DiagnosticSet::from_sorted_entries(
379// vec![DiagnosticEntry {
380// diagnostic: Default::default(),
381// range: Anchor::MIN..Anchor::MAX,
382// }],
383// &buffer.snapshot(),
384// ),
385// cx,
386// );
387// assert_eq!(
388// buffer
389// .snapshot()
390// .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
391// .count(),
392// 1
393// );
394// });
395
396// // When the rename changes the extension of the file, the buffer gets closed on the old
397// // language server and gets opened on the new one.
398// fs.rename(
399// Path::new("/the-root/test3.rs"),
400// Path::new("/the-root/test3.json"),
401// Default::default(),
402// )
403// .await
404// .unwrap();
405// assert_eq!(
406// fake_rust_server
407// .receive_notification::<lsp2::notification::DidCloseTextDocument>()
408// .await
409// .text_document,
410// lsp2::TextDocumentIdentifier::new(lsp2::Url::from_file_path("/the-root/test3.rs").unwrap(),),
411// );
412// assert_eq!(
413// fake_json_server
414// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
415// .await
416// .text_document,
417// lsp2::TextDocumentItem {
418// uri: lsp2::Url::from_file_path("/the-root/test3.json").unwrap(),
419// version: 0,
420// text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
421// language_id: Default::default()
422// },
423// );
424
425// // We clear the diagnostics, since the language has changed.
426// rust_buffer2.read_with(cx, |buffer, _| {
427// assert_eq!(
428// buffer
429// .snapshot()
430// .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
431// .count(),
432// 0
433// );
434// });
435
436// // The renamed file's version resets after changing language server.
437// rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], None, cx));
438// assert_eq!(
439// fake_json_server
440// .receive_notification::<lsp2::notification::DidChangeTextDocument>()
441// .await
442// .text_document,
443// lsp2::VersionedTextDocumentIdentifier::new(
444// lsp2::Url::from_file_path("/the-root/test3.json").unwrap(),
445// 1
446// )
447// );
448
449// // Restart language servers
450// project.update(cx, |project, cx| {
451// project.restart_language_servers_for_buffers(
452// vec![rust_buffer.clone(), json_buffer.clone()],
453// cx,
454// );
455// });
456
457// let mut rust_shutdown_requests = fake_rust_server
458// .handle_request::<lsp2::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
459// let mut json_shutdown_requests = fake_json_server
460// .handle_request::<lsp2::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
461// futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next());
462
463// let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
464// let mut fake_json_server = fake_json_servers.next().await.unwrap();
465
466// // Ensure rust document is reopened in new rust language server
467// assert_eq!(
468// fake_rust_server
469// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
470// .await
471// .text_document,
472// lsp2::TextDocumentItem {
473// uri: lsp2::Url::from_file_path("/the-root/test.rs").unwrap(),
474// version: 0,
475// text: rust_buffer.read_with(cx, |buffer, _| buffer.text()),
476// language_id: Default::default()
477// }
478// );
479
480// // Ensure json documents are reopened in new json language server
481// assert_set_eq!(
482// [
483// fake_json_server
484// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
485// .await
486// .text_document,
487// fake_json_server
488// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
489// .await
490// .text_document,
491// ],
492// [
493// lsp2::TextDocumentItem {
494// uri: lsp2::Url::from_file_path("/the-root/package.json").unwrap(),
495// version: 0,
496// text: json_buffer.read_with(cx, |buffer, _| buffer.text()),
497// language_id: Default::default()
498// },
499// lsp2::TextDocumentItem {
500// uri: lsp2::Url::from_file_path("/the-root/test3.json").unwrap(),
501// version: 0,
502// text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
503// language_id: Default::default()
504// }
505// ]
506// );
507
508// // Close notifications are reported only to servers matching the buffer's language.
509// cx.update(|_| drop(json_buffer));
510// let close_message = lsp2::DidCloseTextDocumentParams {
511// text_document: lsp2::TextDocumentIdentifier::new(
512// lsp2::Url::from_file_path("/the-root/package.json").unwrap(),
513// ),
514// };
515// assert_eq!(
516// fake_json_server
517// .receive_notification::<lsp2::notification::DidCloseTextDocument>()
518// .await,
519// close_message,
520// );
521// }
522
523// #[gpui::test]
524// async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) {
525// init_test(cx);
526
527// let mut language = Language::new(
528// LanguageConfig {
529// name: "Rust".into(),
530// path_suffixes: vec!["rs".to_string()],
531// ..Default::default()
532// },
533// Some(tree_sitter_rust::language()),
534// );
535// let mut fake_servers = language
536// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
537// name: "the-language-server",
538// ..Default::default()
539// }))
540// .await;
541
542// let fs = FakeFs::new(cx.background());
543// fs.insert_tree(
544// "/the-root",
545// json!({
546// ".gitignore": "target\n",
547// "src": {
548// "a.rs": "",
549// "b.rs": "",
550// },
551// "target": {
552// "x": {
553// "out": {
554// "x.rs": ""
555// }
556// },
557// "y": {
558// "out": {
559// "y.rs": "",
560// }
561// },
562// "z": {
563// "out": {
564// "z.rs": ""
565// }
566// }
567// }
568// }),
569// )
570// .await;
571
572// let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
573// project.update(cx, |project, _| {
574// project.languages.add(Arc::new(language));
575// });
576// cx.foreground().run_until_parked();
577
578// // Start the language server by opening a buffer with a compatible file extension.
579// let _buffer = project
580// .update(cx, |project, cx| {
581// project.open_local_buffer("/the-root/src/a.rs", cx)
582// })
583// .await
584// .unwrap();
585
586// // Initially, we don't load ignored files because the language server has not explicitly asked us to watch them.
587// project.read_with(cx, |project, cx| {
588// let worktree = project.worktrees(cx).next().unwrap();
589// assert_eq!(
590// worktree
591// .read(cx)
592// .snapshot()
593// .entries(true)
594// .map(|entry| (entry.path.as_ref(), entry.is_ignored))
595// .collect::<Vec<_>>(),
596// &[
597// (Path::new(""), false),
598// (Path::new(".gitignore"), false),
599// (Path::new("src"), false),
600// (Path::new("src/a.rs"), false),
601// (Path::new("src/b.rs"), false),
602// (Path::new("target"), true),
603// ]
604// );
605// });
606
607// let prev_read_dir_count = fs.read_dir_call_count();
608
609// // Keep track of the FS events reported to the language server.
610// let fake_server = fake_servers.next().await.unwrap();
611// let file_changes = Arc::new(Mutex::new(Vec::new()));
612// fake_server
613// .request::<lsp2::request::RegisterCapability>(lsp2::RegistrationParams {
614// registrations: vec![lsp2::Registration {
615// id: Default::default(),
616// method: "workspace/didChangeWatchedFiles".to_string(),
617// register_options: serde_json::to_value(
618// lsp::DidChangeWatchedFilesRegistrationOptions {
619// watchers: vec![
620// lsp2::FileSystemWatcher {
621// glob_pattern: lsp2::GlobPattern::String(
622// "/the-root/Cargo.toml".to_string(),
623// ),
624// kind: None,
625// },
626// lsp2::FileSystemWatcher {
627// glob_pattern: lsp2::GlobPattern::String(
628// "/the-root/src/*.{rs,c}".to_string(),
629// ),
630// kind: None,
631// },
632// lsp2::FileSystemWatcher {
633// glob_pattern: lsp2::GlobPattern::String(
634// "/the-root/target/y/**/*.rs".to_string(),
635// ),
636// kind: None,
637// },
638// ],
639// },
640// )
641// .ok(),
642// }],
643// })
644// .await
645// .unwrap();
646// fake_server.handle_notification::<lsp2::notification::DidChangeWatchedFiles, _>({
647// let file_changes = file_changes.clone();
648// move |params, _| {
649// let mut file_changes = file_changes.lock();
650// file_changes.extend(params.changes);
651// file_changes.sort_by(|a, b| a.uri.cmp(&b.uri));
652// }
653// });
654
655// cx.foreground().run_until_parked();
656// assert_eq!(mem::take(&mut *file_changes.lock()), &[]);
657// assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4);
658
659// // Now the language server has asked us to watch an ignored directory path,
660// // so we recursively load it.
661// project.read_with(cx, |project, cx| {
662// let worktree = project.worktrees(cx).next().unwrap();
663// assert_eq!(
664// worktree
665// .read(cx)
666// .snapshot()
667// .entries(true)
668// .map(|entry| (entry.path.as_ref(), entry.is_ignored))
669// .collect::<Vec<_>>(),
670// &[
671// (Path::new(""), false),
672// (Path::new(".gitignore"), false),
673// (Path::new("src"), false),
674// (Path::new("src/a.rs"), false),
675// (Path::new("src/b.rs"), false),
676// (Path::new("target"), true),
677// (Path::new("target/x"), true),
678// (Path::new("target/y"), true),
679// (Path::new("target/y/out"), true),
680// (Path::new("target/y/out/y.rs"), true),
681// (Path::new("target/z"), true),
682// ]
683// );
684// });
685
686// // Perform some file system mutations, two of which match the watched patterns,
687// // and one of which does not.
688// fs.create_file("/the-root/src/c.rs".as_ref(), Default::default())
689// .await
690// .unwrap();
691// fs.create_file("/the-root/src/d.txt".as_ref(), Default::default())
692// .await
693// .unwrap();
694// fs.remove_file("/the-root/src/b.rs".as_ref(), Default::default())
695// .await
696// .unwrap();
697// fs.create_file("/the-root/target/x/out/x2.rs".as_ref(), Default::default())
698// .await
699// .unwrap();
700// fs.create_file("/the-root/target/y/out/y2.rs".as_ref(), Default::default())
701// .await
702// .unwrap();
703
704// // The language server receives events for the FS mutations that match its watch patterns.
705// cx.foreground().run_until_parked();
706// assert_eq!(
707// &*file_changes.lock(),
708// &[
709// lsp2::FileEvent {
710// uri: lsp2::Url::from_file_path("/the-root/src/b.rs").unwrap(),
711// typ: lsp2::FileChangeType::DELETED,
712// },
713// lsp2::FileEvent {
714// uri: lsp2::Url::from_file_path("/the-root/src/c.rs").unwrap(),
715// typ: lsp2::FileChangeType::CREATED,
716// },
717// lsp2::FileEvent {
718// uri: lsp2::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(),
719// typ: lsp2::FileChangeType::CREATED,
720// },
721// ]
722// );
723// }
724
725// #[gpui::test]
726// async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
727// init_test(cx);
728
729// let fs = FakeFs::new(cx.background());
730// fs.insert_tree(
731// "/dir",
732// json!({
733// "a.rs": "let a = 1;",
734// "b.rs": "let b = 2;"
735// }),
736// )
737// .await;
738
739// let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await;
740
741// let buffer_a = project
742// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
743// .await
744// .unwrap();
745// let buffer_b = project
746// .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
747// .await
748// .unwrap();
749
750// project.update(cx, |project, cx| {
751// project
752// .update_diagnostics(
753// LanguageServerId(0),
754// lsp::PublishDiagnosticsParams {
755// uri: Url::from_file_path("/dir/a.rs").unwrap(),
756// version: None,
757// diagnostics: vec![lsp2::Diagnostic {
758// range: lsp2::Range::new(
759// lsp2::Position::new(0, 4),
760// lsp2::Position::new(0, 5),
761// ),
762// severity: Some(lsp2::DiagnosticSeverity::ERROR),
763// message: "error 1".to_string(),
764// ..Default::default()
765// }],
766// },
767// &[],
768// cx,
769// )
770// .unwrap();
771// project
772// .update_diagnostics(
773// LanguageServerId(0),
774// lsp::PublishDiagnosticsParams {
775// uri: Url::from_file_path("/dir/b.rs").unwrap(),
776// version: None,
777// diagnostics: vec![lsp2::Diagnostic {
778// range: lsp2::Range::new(
779// lsp2::Position::new(0, 4),
780// lsp2::Position::new(0, 5),
781// ),
782// severity: Some(lsp2::DiagnosticSeverity::WARNING),
783// message: "error 2".to_string(),
784// ..Default::default()
785// }],
786// },
787// &[],
788// cx,
789// )
790// .unwrap();
791// });
792
793// buffer_a.read_with(cx, |buffer, _| {
794// let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
795// assert_eq!(
796// chunks
797// .iter()
798// .map(|(s, d)| (s.as_str(), *d))
799// .collect::<Vec<_>>(),
800// &[
801// ("let ", None),
802// ("a", Some(DiagnosticSeverity::ERROR)),
803// (" = 1;", None),
804// ]
805// );
806// });
807// buffer_b.read_with(cx, |buffer, _| {
808// let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
809// assert_eq!(
810// chunks
811// .iter()
812// .map(|(s, d)| (s.as_str(), *d))
813// .collect::<Vec<_>>(),
814// &[
815// ("let ", None),
816// ("b", Some(DiagnosticSeverity::WARNING)),
817// (" = 2;", None),
818// ]
819// );
820// });
821// }
822
823// #[gpui::test]
824// async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
825// init_test(cx);
826
827// let fs = FakeFs::new(cx.background());
828// fs.insert_tree(
829// "/root",
830// json!({
831// "dir": {
832// "a.rs": "let a = 1;",
833// },
834// "other.rs": "let b = c;"
835// }),
836// )
837// .await;
838
839// let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
840
841// let (worktree, _) = project
842// .update(cx, |project, cx| {
843// project.find_or_create_local_worktree("/root/other.rs", false, cx)
844// })
845// .await
846// .unwrap();
847// let worktree_id = worktree.read_with(cx, |tree, _| tree.id());
848
849// project.update(cx, |project, cx| {
850// project
851// .update_diagnostics(
852// LanguageServerId(0),
853// lsp::PublishDiagnosticsParams {
854// uri: Url::from_file_path("/root/other.rs").unwrap(),
855// version: None,
856// diagnostics: vec![lsp2::Diagnostic {
857// range: lsp2::Range::new(
858// lsp2::Position::new(0, 8),
859// lsp2::Position::new(0, 9),
860// ),
861// severity: Some(lsp2::DiagnosticSeverity::ERROR),
862// message: "unknown variable 'c'".to_string(),
863// ..Default::default()
864// }],
865// },
866// &[],
867// cx,
868// )
869// .unwrap();
870// });
871
872// let buffer = project
873// .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
874// .await
875// .unwrap();
876// buffer.read_with(cx, |buffer, _| {
877// let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
878// assert_eq!(
879// chunks
880// .iter()
881// .map(|(s, d)| (s.as_str(), *d))
882// .collect::<Vec<_>>(),
883// &[
884// ("let b = ", None),
885// ("c", Some(DiagnosticSeverity::ERROR)),
886// (";", None),
887// ]
888// );
889// });
890
891// project.read_with(cx, |project, cx| {
892// assert_eq!(project.diagnostic_summaries(cx).next(), None);
893// assert_eq!(project.diagnostic_summary(cx).error_count, 0);
894// });
895// }
896
897// #[gpui::test]
898// async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
899// init_test(cx);
900
901// let progress_token = "the-progress-token";
902// let mut language = Language::new(
903// LanguageConfig {
904// name: "Rust".into(),
905// path_suffixes: vec!["rs".to_string()],
906// ..Default::default()
907// },
908// Some(tree_sitter_rust::language()),
909// );
910// let mut fake_servers = language
911// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
912// disk_based_diagnostics_progress_token: Some(progress_token.into()),
913// disk_based_diagnostics_sources: vec!["disk".into()],
914// ..Default::default()
915// }))
916// .await;
917
918// let fs = FakeFs::new(cx.background());
919// fs.insert_tree(
920// "/dir",
921// json!({
922// "a.rs": "fn a() { A }",
923// "b.rs": "const y: i32 = 1",
924// }),
925// )
926// .await;
927
928// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
929// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
930// let worktree_id = project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
931
932// // Cause worktree to start the fake language server
933// let _buffer = project
934// .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
935// .await
936// .unwrap();
937
938// let mut events = subscribe(&project, cx);
939
940// let fake_server = fake_servers.next().await.unwrap();
941// assert_eq!(
942// events.next().await.unwrap(),
943// Event::LanguageServerAdded(LanguageServerId(0)),
944// );
945
946// fake_server
947// .start_progress(format!("{}/0", progress_token))
948// .await;
949// assert_eq!(
950// events.next().await.unwrap(),
951// Event::DiskBasedDiagnosticsStarted {
952// language_server_id: LanguageServerId(0),
953// }
954// );
955
956// fake_server.notify::<lsp2::notification::PublishDiagnostics>(lsp2::PublishDiagnosticsParams {
957// uri: Url::from_file_path("/dir/a.rs").unwrap(),
958// version: None,
959// diagnostics: vec![lsp2::Diagnostic {
960// range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)),
961// severity: Some(lsp2::DiagnosticSeverity::ERROR),
962// message: "undefined variable 'A'".to_string(),
963// ..Default::default()
964// }],
965// });
966// assert_eq!(
967// events.next().await.unwrap(),
968// Event::DiagnosticsUpdated {
969// language_server_id: LanguageServerId(0),
970// path: (worktree_id, Path::new("a.rs")).into()
971// }
972// );
973
974// fake_server.end_progress(format!("{}/0", progress_token));
975// assert_eq!(
976// events.next().await.unwrap(),
977// Event::DiskBasedDiagnosticsFinished {
978// language_server_id: LanguageServerId(0)
979// }
980// );
981
982// let buffer = project
983// .update(cx, |p, cx| p.open_local_buffer("/dir/a.rs", cx))
984// .await
985// .unwrap();
986
987// buffer.read_with(cx, |buffer, _| {
988// let snapshot = buffer.snapshot();
989// let diagnostics = snapshot
990// .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
991// .collect::<Vec<_>>();
992// assert_eq!(
993// diagnostics,
994// &[DiagnosticEntry {
995// range: Point::new(0, 9)..Point::new(0, 10),
996// diagnostic: Diagnostic {
997// severity: lsp2::DiagnosticSeverity::ERROR,
998// message: "undefined variable 'A'".to_string(),
999// group_id: 0,
1000// is_primary: true,
1001// ..Default::default()
1002// }
1003// }]
1004// )
1005// });
1006
1007// // Ensure publishing empty diagnostics twice only results in one update event.
1008// fake_server.notify::<lsp2::notification::PublishDiagnostics>(lsp2::PublishDiagnosticsParams {
1009// uri: Url::from_file_path("/dir/a.rs").unwrap(),
1010// version: None,
1011// diagnostics: Default::default(),
1012// });
1013// assert_eq!(
1014// events.next().await.unwrap(),
1015// Event::DiagnosticsUpdated {
1016// language_server_id: LanguageServerId(0),
1017// path: (worktree_id, Path::new("a.rs")).into()
1018// }
1019// );
1020
1021// fake_server.notify::<lsp2::notification::PublishDiagnostics>(lsp2::PublishDiagnosticsParams {
1022// uri: Url::from_file_path("/dir/a.rs").unwrap(),
1023// version: None,
1024// diagnostics: Default::default(),
1025// });
1026// cx.foreground().run_until_parked();
1027// assert_eq!(futures::poll!(events.next()), Poll::Pending);
1028// }
1029
1030// #[gpui::test]
1031// async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) {
1032// init_test(cx);
1033
1034// let progress_token = "the-progress-token";
1035// let mut language = Language::new(
1036// LanguageConfig {
1037// path_suffixes: vec!["rs".to_string()],
1038// ..Default::default()
1039// },
1040// None,
1041// );
1042// let mut fake_servers = language
1043// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1044// disk_based_diagnostics_sources: vec!["disk".into()],
1045// disk_based_diagnostics_progress_token: Some(progress_token.into()),
1046// ..Default::default()
1047// }))
1048// .await;
1049
1050// let fs = FakeFs::new(cx.background());
1051// fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
1052
1053// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1054// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1055
1056// let buffer = project
1057// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1058// .await
1059// .unwrap();
1060
1061// // Simulate diagnostics starting to update.
1062// let fake_server = fake_servers.next().await.unwrap();
1063// fake_server.start_progress(progress_token).await;
1064
1065// // Restart the server before the diagnostics finish updating.
1066// project.update(cx, |project, cx| {
1067// project.restart_language_servers_for_buffers([buffer], cx);
1068// });
1069// let mut events = subscribe(&project, cx);
1070
1071// // Simulate the newly started server sending more diagnostics.
1072// let fake_server = fake_servers.next().await.unwrap();
1073// assert_eq!(
1074// events.next().await.unwrap(),
1075// Event::LanguageServerAdded(LanguageServerId(1))
1076// );
1077// fake_server.start_progress(progress_token).await;
1078// assert_eq!(
1079// events.next().await.unwrap(),
1080// Event::DiskBasedDiagnosticsStarted {
1081// language_server_id: LanguageServerId(1)
1082// }
1083// );
1084// project.read_with(cx, |project, _| {
1085// assert_eq!(
1086// project
1087// .language_servers_running_disk_based_diagnostics()
1088// .collect::<Vec<_>>(),
1089// [LanguageServerId(1)]
1090// );
1091// });
1092
1093// // All diagnostics are considered done, despite the old server's diagnostic
1094// // task never completing.
1095// fake_server.end_progress(progress_token);
1096// assert_eq!(
1097// events.next().await.unwrap(),
1098// Event::DiskBasedDiagnosticsFinished {
1099// language_server_id: LanguageServerId(1)
1100// }
1101// );
1102// project.read_with(cx, |project, _| {
1103// assert_eq!(
1104// project
1105// .language_servers_running_disk_based_diagnostics()
1106// .collect::<Vec<_>>(),
1107// [LanguageServerId(0); 0]
1108// );
1109// });
1110// }
1111
1112// #[gpui::test]
1113// async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAppContext) {
1114// init_test(cx);
1115
1116// let mut language = Language::new(
1117// LanguageConfig {
1118// path_suffixes: vec!["rs".to_string()],
1119// ..Default::default()
1120// },
1121// None,
1122// );
1123// let mut fake_servers = language
1124// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1125// ..Default::default()
1126// }))
1127// .await;
1128
1129// let fs = FakeFs::new(cx.background());
1130// fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
1131
1132// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1133// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1134
1135// let buffer = project
1136// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1137// .await
1138// .unwrap();
1139
1140// // Publish diagnostics
1141// let fake_server = fake_servers.next().await.unwrap();
1142// fake_server.notify::<lsp2::notification::PublishDiagnostics>(lsp2::PublishDiagnosticsParams {
1143// uri: Url::from_file_path("/dir/a.rs").unwrap(),
1144// version: None,
1145// diagnostics: vec![lsp2::Diagnostic {
1146// range: lsp2::Range::new(lsp2::Position::new(0, 0), lsp2::Position::new(0, 0)),
1147// severity: Some(lsp2::DiagnosticSeverity::ERROR),
1148// message: "the message".to_string(),
1149// ..Default::default()
1150// }],
1151// });
1152
1153// cx.foreground().run_until_parked();
1154// buffer.read_with(cx, |buffer, _| {
1155// assert_eq!(
1156// buffer
1157// .snapshot()
1158// .diagnostics_in_range::<_, usize>(0..1, false)
1159// .map(|entry| entry.diagnostic.message.clone())
1160// .collect::<Vec<_>>(),
1161// ["the message".to_string()]
1162// );
1163// });
1164// project.read_with(cx, |project, cx| {
1165// assert_eq!(
1166// project.diagnostic_summary(cx),
1167// DiagnosticSummary {
1168// error_count: 1,
1169// warning_count: 0,
1170// }
1171// );
1172// });
1173
1174// project.update(cx, |project, cx| {
1175// project.restart_language_servers_for_buffers([buffer.clone()], cx);
1176// });
1177
1178// // The diagnostics are cleared.
1179// cx.foreground().run_until_parked();
1180// buffer.read_with(cx, |buffer, _| {
1181// assert_eq!(
1182// buffer
1183// .snapshot()
1184// .diagnostics_in_range::<_, usize>(0..1, false)
1185// .map(|entry| entry.diagnostic.message.clone())
1186// .collect::<Vec<_>>(),
1187// Vec::<String>::new(),
1188// );
1189// });
1190// project.read_with(cx, |project, cx| {
1191// assert_eq!(
1192// project.diagnostic_summary(cx),
1193// DiagnosticSummary {
1194// error_count: 0,
1195// warning_count: 0,
1196// }
1197// );
1198// });
1199// }
1200
1201// #[gpui::test]
1202// async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) {
1203// init_test(cx);
1204
1205// let mut language = Language::new(
1206// LanguageConfig {
1207// path_suffixes: vec!["rs".to_string()],
1208// ..Default::default()
1209// },
1210// None,
1211// );
1212// let mut fake_servers = language
1213// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1214// name: "the-lsp",
1215// ..Default::default()
1216// }))
1217// .await;
1218
1219// let fs = FakeFs::new(cx.background());
1220// fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
1221
1222// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1223// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1224
1225// let buffer = project
1226// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1227// .await
1228// .unwrap();
1229
1230// // Before restarting the server, report diagnostics with an unknown buffer version.
1231// let fake_server = fake_servers.next().await.unwrap();
1232// fake_server.notify::<lsp2::notification::PublishDiagnostics>(lsp2::PublishDiagnosticsParams {
1233// uri: lsp2::Url::from_file_path("/dir/a.rs").unwrap(),
1234// version: Some(10000),
1235// diagnostics: Vec::new(),
1236// });
1237// cx.foreground().run_until_parked();
1238
1239// project.update(cx, |project, cx| {
1240// project.restart_language_servers_for_buffers([buffer.clone()], cx);
1241// });
1242// let mut fake_server = fake_servers.next().await.unwrap();
1243// let notification = fake_server
1244// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
1245// .await
1246// .text_document;
1247// assert_eq!(notification.version, 0);
1248// }
1249
1250// #[gpui::test]
1251// async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
1252// init_test(cx);
1253
1254// let mut rust = Language::new(
1255// LanguageConfig {
1256// name: Arc::from("Rust"),
1257// path_suffixes: vec!["rs".to_string()],
1258// ..Default::default()
1259// },
1260// None,
1261// );
1262// let mut fake_rust_servers = rust
1263// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1264// name: "rust-lsp",
1265// ..Default::default()
1266// }))
1267// .await;
1268// let mut js = Language::new(
1269// LanguageConfig {
1270// name: Arc::from("JavaScript"),
1271// path_suffixes: vec!["js".to_string()],
1272// ..Default::default()
1273// },
1274// None,
1275// );
1276// let mut fake_js_servers = js
1277// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1278// name: "js-lsp",
1279// ..Default::default()
1280// }))
1281// .await;
1282
1283// let fs = FakeFs::new(cx.background());
1284// fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
1285// .await;
1286
1287// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1288// project.update(cx, |project, _| {
1289// project.languages.add(Arc::new(rust));
1290// project.languages.add(Arc::new(js));
1291// });
1292
1293// let _rs_buffer = project
1294// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1295// .await
1296// .unwrap();
1297// let _js_buffer = project
1298// .update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx))
1299// .await
1300// .unwrap();
1301
1302// let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap();
1303// assert_eq!(
1304// fake_rust_server_1
1305// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
1306// .await
1307// .text_document
1308// .uri
1309// .as_str(),
1310// "file:///dir/a.rs"
1311// );
1312
1313// let mut fake_js_server = fake_js_servers.next().await.unwrap();
1314// assert_eq!(
1315// fake_js_server
1316// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
1317// .await
1318// .text_document
1319// .uri
1320// .as_str(),
1321// "file:///dir/b.js"
1322// );
1323
1324// // Disable Rust language server, ensuring only that server gets stopped.
1325// cx.update(|cx| {
1326// cx.update_global(|settings: &mut SettingsStore, cx| {
1327// settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1328// settings.languages.insert(
1329// Arc::from("Rust"),
1330// LanguageSettingsContent {
1331// enable_language_server: Some(false),
1332// ..Default::default()
1333// },
1334// );
1335// });
1336// })
1337// });
1338// fake_rust_server_1
1339// .receive_notification::<lsp2::notification::Exit>()
1340// .await;
1341
1342// // Enable Rust and disable JavaScript language servers, ensuring that the
1343// // former gets started again and that the latter stops.
1344// cx.update(|cx| {
1345// cx.update_global(|settings: &mut SettingsStore, cx| {
1346// settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1347// settings.languages.insert(
1348// Arc::from("Rust"),
1349// LanguageSettingsContent {
1350// enable_language_server: Some(true),
1351// ..Default::default()
1352// },
1353// );
1354// settings.languages.insert(
1355// Arc::from("JavaScript"),
1356// LanguageSettingsContent {
1357// enable_language_server: Some(false),
1358// ..Default::default()
1359// },
1360// );
1361// });
1362// })
1363// });
1364// let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap();
1365// assert_eq!(
1366// fake_rust_server_2
1367// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
1368// .await
1369// .text_document
1370// .uri
1371// .as_str(),
1372// "file:///dir/a.rs"
1373// );
1374// fake_js_server
1375// .receive_notification::<lsp2::notification::Exit>()
1376// .await;
1377// }
1378
1379// #[gpui::test(iterations = 3)]
1380// async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
1381// init_test(cx);
1382
1383// let mut language = Language::new(
1384// LanguageConfig {
1385// name: "Rust".into(),
1386// path_suffixes: vec!["rs".to_string()],
1387// ..Default::default()
1388// },
1389// Some(tree_sitter_rust::language()),
1390// );
1391// let mut fake_servers = language
1392// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1393// disk_based_diagnostics_sources: vec!["disk".into()],
1394// ..Default::default()
1395// }))
1396// .await;
1397
1398// let text = "
1399// fn a() { A }
1400// fn b() { BB }
1401// fn c() { CCC }
1402// "
1403// .unindent();
1404
1405// let fs = FakeFs::new(cx.background());
1406// fs.insert_tree("/dir", json!({ "a.rs": text })).await;
1407
1408// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1409// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1410
1411// let buffer = project
1412// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1413// .await
1414// .unwrap();
1415
1416// let mut fake_server = fake_servers.next().await.unwrap();
1417// let open_notification = fake_server
1418// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
1419// .await;
1420
1421// // Edit the buffer, moving the content down
1422// buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], None, cx));
1423// let change_notification_1 = fake_server
1424// .receive_notification::<lsp2::notification::DidChangeTextDocument>()
1425// .await;
1426// assert!(change_notification_1.text_document.version > open_notification.text_document.version);
1427
1428// // Report some diagnostics for the initial version of the buffer
1429// fake_server.notify::<lsp2::notification::PublishDiagnostics>(lsp2::PublishDiagnosticsParams {
1430// uri: lsp2::Url::from_file_path("/dir/a.rs").unwrap(),
1431// version: Some(open_notification.text_document.version),
1432// diagnostics: vec![
1433// lsp2::Diagnostic {
1434// range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)),
1435// severity: Some(DiagnosticSeverity::ERROR),
1436// message: "undefined variable 'A'".to_string(),
1437// source: Some("disk".to_string()),
1438// ..Default::default()
1439// },
1440// lsp2::Diagnostic {
1441// range: lsp2::Range::new(lsp2::Position::new(1, 9), lsp2::Position::new(1, 11)),
1442// severity: Some(DiagnosticSeverity::ERROR),
1443// message: "undefined variable 'BB'".to_string(),
1444// source: Some("disk".to_string()),
1445// ..Default::default()
1446// },
1447// lsp2::Diagnostic {
1448// range: lsp2::Range::new(lsp2::Position::new(2, 9), lsp2::Position::new(2, 12)),
1449// severity: Some(DiagnosticSeverity::ERROR),
1450// source: Some("disk".to_string()),
1451// message: "undefined variable 'CCC'".to_string(),
1452// ..Default::default()
1453// },
1454// ],
1455// });
1456
1457// // The diagnostics have moved down since they were created.
1458// buffer.next_notification(cx).await;
1459// cx.foreground().run_until_parked();
1460// buffer.read_with(cx, |buffer, _| {
1461// assert_eq!(
1462// buffer
1463// .snapshot()
1464// .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0), false)
1465// .collect::<Vec<_>>(),
1466// &[
1467// DiagnosticEntry {
1468// range: Point::new(3, 9)..Point::new(3, 11),
1469// diagnostic: Diagnostic {
1470// source: Some("disk".into()),
1471// severity: DiagnosticSeverity::ERROR,
1472// message: "undefined variable 'BB'".to_string(),
1473// is_disk_based: true,
1474// group_id: 1,
1475// is_primary: true,
1476// ..Default::default()
1477// },
1478// },
1479// DiagnosticEntry {
1480// range: Point::new(4, 9)..Point::new(4, 12),
1481// diagnostic: Diagnostic {
1482// source: Some("disk".into()),
1483// severity: DiagnosticSeverity::ERROR,
1484// message: "undefined variable 'CCC'".to_string(),
1485// is_disk_based: true,
1486// group_id: 2,
1487// is_primary: true,
1488// ..Default::default()
1489// }
1490// }
1491// ]
1492// );
1493// assert_eq!(
1494// chunks_with_diagnostics(buffer, 0..buffer.len()),
1495// [
1496// ("\n\nfn a() { ".to_string(), None),
1497// ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
1498// (" }\nfn b() { ".to_string(), None),
1499// ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
1500// (" }\nfn c() { ".to_string(), None),
1501// ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
1502// (" }\n".to_string(), None),
1503// ]
1504// );
1505// assert_eq!(
1506// chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
1507// [
1508// ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
1509// (" }\nfn c() { ".to_string(), None),
1510// ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
1511// ]
1512// );
1513// });
1514
1515// // Ensure overlapping diagnostics are highlighted correctly.
1516// fake_server.notify::<lsp2::notification::PublishDiagnostics>(lsp2::PublishDiagnosticsParams {
1517// uri: lsp2::Url::from_file_path("/dir/a.rs").unwrap(),
1518// version: Some(open_notification.text_document.version),
1519// diagnostics: vec![
1520// lsp2::Diagnostic {
1521// range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)),
1522// severity: Some(DiagnosticSeverity::ERROR),
1523// message: "undefined variable 'A'".to_string(),
1524// source: Some("disk".to_string()),
1525// ..Default::default()
1526// },
1527// lsp2::Diagnostic {
1528// range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 12)),
1529// severity: Some(DiagnosticSeverity::WARNING),
1530// message: "unreachable statement".to_string(),
1531// source: Some("disk".to_string()),
1532// ..Default::default()
1533// },
1534// ],
1535// });
1536
1537// buffer.next_notification(cx).await;
1538// cx.foreground().run_until_parked();
1539// buffer.read_with(cx, |buffer, _| {
1540// assert_eq!(
1541// buffer
1542// .snapshot()
1543// .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0), false)
1544// .collect::<Vec<_>>(),
1545// &[
1546// DiagnosticEntry {
1547// range: Point::new(2, 9)..Point::new(2, 12),
1548// diagnostic: Diagnostic {
1549// source: Some("disk".into()),
1550// severity: DiagnosticSeverity::WARNING,
1551// message: "unreachable statement".to_string(),
1552// is_disk_based: true,
1553// group_id: 4,
1554// is_primary: true,
1555// ..Default::default()
1556// }
1557// },
1558// DiagnosticEntry {
1559// range: Point::new(2, 9)..Point::new(2, 10),
1560// diagnostic: Diagnostic {
1561// source: Some("disk".into()),
1562// severity: DiagnosticSeverity::ERROR,
1563// message: "undefined variable 'A'".to_string(),
1564// is_disk_based: true,
1565// group_id: 3,
1566// is_primary: true,
1567// ..Default::default()
1568// },
1569// }
1570// ]
1571// );
1572// assert_eq!(
1573// chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
1574// [
1575// ("fn a() { ".to_string(), None),
1576// ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
1577// (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
1578// ("\n".to_string(), None),
1579// ]
1580// );
1581// assert_eq!(
1582// chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
1583// [
1584// (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
1585// ("\n".to_string(), None),
1586// ]
1587// );
1588// });
1589
1590// // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
1591// // changes since the last save.
1592// buffer.update(cx, |buffer, cx| {
1593// buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
1594// buffer.edit(
1595// [(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")],
1596// None,
1597// cx,
1598// );
1599// buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], None, cx);
1600// });
1601// let change_notification_2 = fake_server
1602// .receive_notification::<lsp2::notification::DidChangeTextDocument>()
1603// .await;
1604// assert!(
1605// change_notification_2.text_document.version > change_notification_1.text_document.version
1606// );
1607
1608// // Handle out-of-order diagnostics
1609// fake_server.notify::<lsp2::notification::PublishDiagnostics>(lsp2::PublishDiagnosticsParams {
1610// uri: lsp2::Url::from_file_path("/dir/a.rs").unwrap(),
1611// version: Some(change_notification_2.text_document.version),
1612// diagnostics: vec![
1613// lsp2::Diagnostic {
1614// range: lsp2::Range::new(lsp2::Position::new(1, 9), lsp2::Position::new(1, 11)),
1615// severity: Some(DiagnosticSeverity::ERROR),
1616// message: "undefined variable 'BB'".to_string(),
1617// source: Some("disk".to_string()),
1618// ..Default::default()
1619// },
1620// lsp2::Diagnostic {
1621// range: lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)),
1622// severity: Some(DiagnosticSeverity::WARNING),
1623// message: "undefined variable 'A'".to_string(),
1624// source: Some("disk".to_string()),
1625// ..Default::default()
1626// },
1627// ],
1628// });
1629
1630// buffer.next_notification(cx).await;
1631// cx.foreground().run_until_parked();
1632// buffer.read_with(cx, |buffer, _| {
1633// assert_eq!(
1634// buffer
1635// .snapshot()
1636// .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
1637// .collect::<Vec<_>>(),
1638// &[
1639// DiagnosticEntry {
1640// range: Point::new(2, 21)..Point::new(2, 22),
1641// diagnostic: Diagnostic {
1642// source: Some("disk".into()),
1643// severity: DiagnosticSeverity::WARNING,
1644// message: "undefined variable 'A'".to_string(),
1645// is_disk_based: true,
1646// group_id: 6,
1647// is_primary: true,
1648// ..Default::default()
1649// }
1650// },
1651// DiagnosticEntry {
1652// range: Point::new(3, 9)..Point::new(3, 14),
1653// diagnostic: Diagnostic {
1654// source: Some("disk".into()),
1655// severity: DiagnosticSeverity::ERROR,
1656// message: "undefined variable 'BB'".to_string(),
1657// is_disk_based: true,
1658// group_id: 5,
1659// is_primary: true,
1660// ..Default::default()
1661// },
1662// }
1663// ]
1664// );
1665// });
1666// }
1667
1668// #[gpui::test]
1669// async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
1670// init_test(cx);
1671
1672// let text = concat!(
1673// "let one = ;\n", //
1674// "let two = \n",
1675// "let three = 3;\n",
1676// );
1677
1678// let fs = FakeFs::new(cx.background());
1679// fs.insert_tree("/dir", json!({ "a.rs": text })).await;
1680
1681// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1682// let buffer = project
1683// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1684// .await
1685// .unwrap();
1686
1687// project.update(cx, |project, cx| {
1688// project
1689// .update_buffer_diagnostics(
1690// &buffer,
1691// LanguageServerId(0),
1692// None,
1693// vec![
1694// DiagnosticEntry {
1695// range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)),
1696// diagnostic: Diagnostic {
1697// severity: DiagnosticSeverity::ERROR,
1698// message: "syntax error 1".to_string(),
1699// ..Default::default()
1700// },
1701// },
1702// DiagnosticEntry {
1703// range: Unclipped(PointUtf16::new(1, 10))..Unclipped(PointUtf16::new(1, 10)),
1704// diagnostic: Diagnostic {
1705// severity: DiagnosticSeverity::ERROR,
1706// message: "syntax error 2".to_string(),
1707// ..Default::default()
1708// },
1709// },
1710// ],
1711// cx,
1712// )
1713// .unwrap();
1714// });
1715
1716// // An empty range is extended forward to include the following character.
1717// // At the end of a line, an empty range is extended backward to include
1718// // the preceding character.
1719// buffer.read_with(cx, |buffer, _| {
1720// let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
1721// assert_eq!(
1722// chunks
1723// .iter()
1724// .map(|(s, d)| (s.as_str(), *d))
1725// .collect::<Vec<_>>(),
1726// &[
1727// ("let one = ", None),
1728// (";", Some(DiagnosticSeverity::ERROR)),
1729// ("\nlet two =", None),
1730// (" ", Some(DiagnosticSeverity::ERROR)),
1731// ("\nlet three = 3;\n", None)
1732// ]
1733// );
1734// });
1735// }
1736
1737// #[gpui::test]
1738// async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
1739// init_test(cx);
1740
1741// let fs = FakeFs::new(cx.background());
1742// fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
1743// .await;
1744
1745// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1746
1747// project.update(cx, |project, cx| {
1748// project
1749// .update_diagnostic_entries(
1750// LanguageServerId(0),
1751// Path::new("/dir/a.rs").to_owned(),
1752// None,
1753// vec![DiagnosticEntry {
1754// range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)),
1755// diagnostic: Diagnostic {
1756// severity: DiagnosticSeverity::ERROR,
1757// is_primary: true,
1758// message: "syntax error a1".to_string(),
1759// ..Default::default()
1760// },
1761// }],
1762// cx,
1763// )
1764// .unwrap();
1765// project
1766// .update_diagnostic_entries(
1767// LanguageServerId(1),
1768// Path::new("/dir/a.rs").to_owned(),
1769// None,
1770// vec![DiagnosticEntry {
1771// range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)),
1772// diagnostic: Diagnostic {
1773// severity: DiagnosticSeverity::ERROR,
1774// is_primary: true,
1775// message: "syntax error b1".to_string(),
1776// ..Default::default()
1777// },
1778// }],
1779// cx,
1780// )
1781// .unwrap();
1782
1783// assert_eq!(
1784// project.diagnostic_summary(cx),
1785// DiagnosticSummary {
1786// error_count: 2,
1787// warning_count: 0,
1788// }
1789// );
1790// });
1791// }
1792
1793// #[gpui::test]
1794// async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
1795// init_test(cx);
1796
1797// let mut language = Language::new(
1798// LanguageConfig {
1799// name: "Rust".into(),
1800// path_suffixes: vec!["rs".to_string()],
1801// ..Default::default()
1802// },
1803// Some(tree_sitter_rust::language()),
1804// );
1805// let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
1806
1807// let text = "
1808// fn a() {
1809// f1();
1810// }
1811// fn b() {
1812// f2();
1813// }
1814// fn c() {
1815// f3();
1816// }
1817// "
1818// .unindent();
1819
1820// let fs = FakeFs::new(cx.background());
1821// fs.insert_tree(
1822// "/dir",
1823// json!({
1824// "a.rs": text.clone(),
1825// }),
1826// )
1827// .await;
1828
1829// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1830// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1831// let buffer = project
1832// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1833// .await
1834// .unwrap();
1835
1836// let mut fake_server = fake_servers.next().await.unwrap();
1837// let lsp_document_version = fake_server
1838// .receive_notification::<lsp2::notification::DidOpenTextDocument>()
1839// .await
1840// .text_document
1841// .version;
1842
1843// // Simulate editing the buffer after the language server computes some edits.
1844// buffer.update(cx, |buffer, cx| {
1845// buffer.edit(
1846// [(
1847// Point::new(0, 0)..Point::new(0, 0),
1848// "// above first function\n",
1849// )],
1850// None,
1851// cx,
1852// );
1853// buffer.edit(
1854// [(
1855// Point::new(2, 0)..Point::new(2, 0),
1856// " // inside first function\n",
1857// )],
1858// None,
1859// cx,
1860// );
1861// buffer.edit(
1862// [(
1863// Point::new(6, 4)..Point::new(6, 4),
1864// "// inside second function ",
1865// )],
1866// None,
1867// cx,
1868// );
1869
1870// assert_eq!(
1871// buffer.text(),
1872// "
1873// // above first function
1874// fn a() {
1875// // inside first function
1876// f1();
1877// }
1878// fn b() {
1879// // inside second function f2();
1880// }
1881// fn c() {
1882// f3();
1883// }
1884// "
1885// .unindent()
1886// );
1887// });
1888
1889// let edits = project
1890// .update(cx, |project, cx| {
1891// project.edits_from_lsp(
1892// &buffer,
1893// vec![
1894// // replace body of first function
1895// lsp2::TextEdit {
1896// range: lsp2::Range::new(
1897// lsp2::Position::new(0, 0),
1898// lsp2::Position::new(3, 0),
1899// ),
1900// new_text: "
1901// fn a() {
1902// f10();
1903// }
1904// "
1905// .unindent(),
1906// },
1907// // edit inside second function
1908// lsp2::TextEdit {
1909// range: lsp2::Range::new(
1910// lsp2::Position::new(4, 6),
1911// lsp2::Position::new(4, 6),
1912// ),
1913// new_text: "00".into(),
1914// },
1915// // edit inside third function via two distinct edits
1916// lsp2::TextEdit {
1917// range: lsp2::Range::new(
1918// lsp2::Position::new(7, 5),
1919// lsp2::Position::new(7, 5),
1920// ),
1921// new_text: "4000".into(),
1922// },
1923// lsp2::TextEdit {
1924// range: lsp2::Range::new(
1925// lsp2::Position::new(7, 5),
1926// lsp2::Position::new(7, 6),
1927// ),
1928// new_text: "".into(),
1929// },
1930// ],
1931// LanguageServerId(0),
1932// Some(lsp_document_version),
1933// cx,
1934// )
1935// })
1936// .await
1937// .unwrap();
1938
1939// buffer.update(cx, |buffer, cx| {
1940// for (range, new_text) in edits {
1941// buffer.edit([(range, new_text)], None, cx);
1942// }
1943// assert_eq!(
1944// buffer.text(),
1945// "
1946// // above first function
1947// fn a() {
1948// // inside first function
1949// f10();
1950// }
1951// fn b() {
1952// // inside second function f200();
1953// }
1954// fn c() {
1955// f4000();
1956// }
1957// "
1958// .unindent()
1959// );
1960// });
1961// }
1962
1963// #[gpui::test]
1964// async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
1965// init_test(cx);
1966
1967// let text = "
1968// use a::b;
1969// use a::c;
1970
1971// fn f() {
1972// b();
1973// c();
1974// }
1975// "
1976// .unindent();
1977
1978// let fs = FakeFs::new(cx.background());
1979// fs.insert_tree(
1980// "/dir",
1981// json!({
1982// "a.rs": text.clone(),
1983// }),
1984// )
1985// .await;
1986
1987// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1988// let buffer = project
1989// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1990// .await
1991// .unwrap();
1992
1993// // Simulate the language server sending us a small edit in the form of a very large diff.
1994// // Rust-analyzer does this when performing a merge-imports code action.
1995// let edits = project
1996// .update(cx, |project, cx| {
1997// project.edits_from_lsp(
1998// &buffer,
1999// [
2000// // Replace the first use statement without editing the semicolon.
2001// lsp2::TextEdit {
2002// range: lsp2::Range::new(
2003// lsp2::Position::new(0, 4),
2004// lsp2::Position::new(0, 8),
2005// ),
2006// new_text: "a::{b, c}".into(),
2007// },
2008// // Reinsert the remainder of the file between the semicolon and the final
2009// // newline of the file.
2010// lsp2::TextEdit {
2011// range: lsp2::Range::new(
2012// lsp2::Position::new(0, 9),
2013// lsp2::Position::new(0, 9),
2014// ),
2015// new_text: "\n\n".into(),
2016// },
2017// lsp2::TextEdit {
2018// range: lsp2::Range::new(
2019// lsp2::Position::new(0, 9),
2020// lsp2::Position::new(0, 9),
2021// ),
2022// new_text: "
2023// fn f() {
2024// b();
2025// c();
2026// }"
2027// .unindent(),
2028// },
2029// // Delete everything after the first newline of the file.
2030// lsp2::TextEdit {
2031// range: lsp2::Range::new(
2032// lsp2::Position::new(1, 0),
2033// lsp2::Position::new(7, 0),
2034// ),
2035// new_text: "".into(),
2036// },
2037// ],
2038// LanguageServerId(0),
2039// None,
2040// cx,
2041// )
2042// })
2043// .await
2044// .unwrap();
2045
2046// buffer.update(cx, |buffer, cx| {
2047// let edits = edits
2048// .into_iter()
2049// .map(|(range, text)| {
2050// (
2051// range.start.to_point(buffer)..range.end.to_point(buffer),
2052// text,
2053// )
2054// })
2055// .collect::<Vec<_>>();
2056
2057// assert_eq!(
2058// edits,
2059// [
2060// (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
2061// (Point::new(1, 0)..Point::new(2, 0), "".into())
2062// ]
2063// );
2064
2065// for (range, new_text) in edits {
2066// buffer.edit([(range, new_text)], None, cx);
2067// }
2068// assert_eq!(
2069// buffer.text(),
2070// "
2071// use a::{b, c};
2072
2073// fn f() {
2074// b();
2075// c();
2076// }
2077// "
2078// .unindent()
2079// );
2080// });
2081// }
2082
2083// #[gpui::test]
2084// async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
2085// init_test(cx);
2086
2087// let text = "
2088// use a::b;
2089// use a::c;
2090
2091// fn f() {
2092// b();
2093// c();
2094// }
2095// "
2096// .unindent();
2097
2098// let fs = FakeFs::new(cx.background());
2099// fs.insert_tree(
2100// "/dir",
2101// json!({
2102// "a.rs": text.clone(),
2103// }),
2104// )
2105// .await;
2106
2107// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
2108// let buffer = project
2109// .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
2110// .await
2111// .unwrap();
2112
2113// // Simulate the language server sending us edits in a non-ordered fashion,
2114// // with ranges sometimes being inverted or pointing to invalid locations.
2115// let edits = project
2116// .update(cx, |project, cx| {
2117// project.edits_from_lsp(
2118// &buffer,
2119// [
2120// lsp2::TextEdit {
2121// range: lsp2::Range::new(
2122// lsp2::Position::new(0, 9),
2123// lsp2::Position::new(0, 9),
2124// ),
2125// new_text: "\n\n".into(),
2126// },
2127// lsp2::TextEdit {
2128// range: lsp2::Range::new(
2129// lsp2::Position::new(0, 8),
2130// lsp2::Position::new(0, 4),
2131// ),
2132// new_text: "a::{b, c}".into(),
2133// },
2134// lsp2::TextEdit {
2135// range: lsp2::Range::new(
2136// lsp2::Position::new(1, 0),
2137// lsp2::Position::new(99, 0),
2138// ),
2139// new_text: "".into(),
2140// },
2141// lsp2::TextEdit {
2142// range: lsp2::Range::new(
2143// lsp2::Position::new(0, 9),
2144// lsp2::Position::new(0, 9),
2145// ),
2146// new_text: "
2147// fn f() {
2148// b();
2149// c();
2150// }"
2151// .unindent(),
2152// },
2153// ],
2154// LanguageServerId(0),
2155// None,
2156// cx,
2157// )
2158// })
2159// .await
2160// .unwrap();
2161
2162// buffer.update(cx, |buffer, cx| {
2163// let edits = edits
2164// .into_iter()
2165// .map(|(range, text)| {
2166// (
2167// range.start.to_point(buffer)..range.end.to_point(buffer),
2168// text,
2169// )
2170// })
2171// .collect::<Vec<_>>();
2172
2173// assert_eq!(
2174// edits,
2175// [
2176// (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
2177// (Point::new(1, 0)..Point::new(2, 0), "".into())
2178// ]
2179// );
2180
2181// for (range, new_text) in edits {
2182// buffer.edit([(range, new_text)], None, cx);
2183// }
2184// assert_eq!(
2185// buffer.text(),
2186// "
2187// use a::{b, c};
2188
2189// fn f() {
2190// b();
2191// c();
2192// }
2193// "
2194// .unindent()
2195// );
2196// });
2197// }
2198
2199// fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
2200// buffer: &Buffer,
2201// range: Range<T>,
2202// ) -> Vec<(String, Option<DiagnosticSeverity>)> {
2203// let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
2204// for chunk in buffer.snapshot().chunks(range, true) {
2205// if chunks.last().map_or(false, |prev_chunk| {
2206// prev_chunk.1 == chunk.diagnostic_severity
2207// }) {
2208// chunks.last_mut().unwrap().0.push_str(chunk.text);
2209// } else {
2210// chunks.push((chunk.text.to_string(), chunk.diagnostic_severity));
2211// }
2212// }
2213// chunks
2214// }
2215
2216// #[gpui::test(iterations = 10)]
2217// async fn test_definition(cx: &mut gpui::TestAppContext) {
2218// init_test(cx);
2219
2220// let mut language = Language::new(
2221// LanguageConfig {
2222// name: "Rust".into(),
2223// path_suffixes: vec!["rs".to_string()],
2224// ..Default::default()
2225// },
2226// Some(tree_sitter_rust::language()),
2227// );
2228// let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
2229
2230// let fs = FakeFs::new(cx.background());
2231// fs.insert_tree(
2232// "/dir",
2233// json!({
2234// "a.rs": "const fn a() { A }",
2235// "b.rs": "const y: i32 = crate::a()",
2236// }),
2237// )
2238// .await;
2239
2240// let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await;
2241// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
2242
2243// let buffer = project
2244// .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
2245// .await
2246// .unwrap();
2247
2248// let fake_server = fake_servers.next().await.unwrap();
2249// fake_server.handle_request::<lsp2::request::GotoDefinition, _, _>(|params, _| async move {
2250// let params = params.text_document_position_params;
2251// assert_eq!(
2252// params.text_document.uri.to_file_path().unwrap(),
2253// Path::new("/dir/b.rs"),
2254// );
2255// assert_eq!(params.position, lsp2::Position::new(0, 22));
2256
2257// Ok(Some(lsp2::GotoDefinitionResponse::Scalar(
2258// lsp2::Location::new(
2259// lsp2::Url::from_file_path("/dir/a.rs").unwrap(),
2260// lsp2::Range::new(lsp2::Position::new(0, 9), lsp2::Position::new(0, 10)),
2261// ),
2262// )))
2263// });
2264
2265// let mut definitions = project
2266// .update(cx, |project, cx| project.definition(&buffer, 22, cx))
2267// .await
2268// .unwrap();
2269
2270// // Assert no new language server started
2271// cx.foreground().run_until_parked();
2272// assert!(fake_servers.try_next().is_err());
2273
2274// assert_eq!(definitions.len(), 1);
2275// let definition = definitions.pop().unwrap();
2276// cx.update(|cx| {
2277// let target_buffer = definition.target.buffer.read(cx);
2278// assert_eq!(
2279// target_buffer
2280// .file()
2281// .unwrap()
2282// .as_local()
2283// .unwrap()
2284// .abs_path(cx),
2285// Path::new("/dir/a.rs"),
2286// );
2287// assert_eq!(definition.target.range.to_offset(target_buffer), 9..10);
2288// assert_eq!(
2289// list_worktrees(&project, cx),
2290// [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)]
2291// );
2292
2293// drop(definition);
2294// });
2295// cx.read(|cx| {
2296// assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]);
2297// });
2298
2299// fn list_worktrees<'a>(
2300// project: &'a ModelHandle<Project>,
2301// cx: &'a AppContext,
2302// ) -> Vec<(&'a Path, bool)> {
2303// project
2304// .read(cx)
2305// .worktrees(cx)
2306// .map(|worktree| {
2307// let worktree = worktree.read(cx);
2308// (
2309// worktree.as_local().unwrap().abs_path().as_ref(),
2310// worktree.is_visible(),
2311// )
2312// })
2313// .collect::<Vec<_>>()
2314// }
2315// }
2316
2317// #[gpui::test]
2318// async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
2319// init_test(cx);
2320
2321// let mut language = Language::new(
2322// LanguageConfig {
2323// name: "TypeScript".into(),
2324// path_suffixes: vec!["ts".to_string()],
2325// ..Default::default()
2326// },
2327// Some(tree_sitter_typescript::language_typescript()),
2328// );
2329// let mut fake_language_servers = language
2330// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2331// capabilities: lsp::ServerCapabilities {
2332// completion_provider: Some(lsp::CompletionOptions {
2333// trigger_characters: Some(vec![":".to_string()]),
2334// ..Default::default()
2335// }),
2336// ..Default::default()
2337// },
2338// ..Default::default()
2339// }))
2340// .await;
2341
2342// let fs = FakeFs::new(cx.background());
2343// fs.insert_tree(
2344// "/dir",
2345// json!({
2346// "a.ts": "",
2347// }),
2348// )
2349// .await;
2350
2351// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
2352// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
2353// let buffer = project
2354// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
2355// .await
2356// .unwrap();
2357
2358// let fake_server = fake_language_servers.next().await.unwrap();
2359
2360// let text = "let a = b.fqn";
2361// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
2362// let completions = project.update(cx, |project, cx| {
2363// project.completions(&buffer, text.len(), cx)
2364// });
2365
2366// fake_server
2367// .handle_request::<lsp2::request::Completion, _, _>(|_, _| async move {
2368// Ok(Some(lsp2::CompletionResponse::Array(vec![
2369// lsp2::CompletionItem {
2370// label: "fullyQualifiedName?".into(),
2371// insert_text: Some("fullyQualifiedName".into()),
2372// ..Default::default()
2373// },
2374// ])))
2375// })
2376// .next()
2377// .await;
2378// let completions = completions.await.unwrap();
2379// let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
2380// assert_eq!(completions.len(), 1);
2381// assert_eq!(completions[0].new_text, "fullyQualifiedName");
2382// assert_eq!(
2383// completions[0].old_range.to_offset(&snapshot),
2384// text.len() - 3..text.len()
2385// );
2386
2387// let text = "let a = \"atoms/cmp\"";
2388// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
2389// let completions = project.update(cx, |project, cx| {
2390// project.completions(&buffer, text.len() - 1, cx)
2391// });
2392
2393// fake_server
2394// .handle_request::<lsp2::request::Completion, _, _>(|_, _| async move {
2395// Ok(Some(lsp2::CompletionResponse::Array(vec![
2396// lsp2::CompletionItem {
2397// label: "component".into(),
2398// ..Default::default()
2399// },
2400// ])))
2401// })
2402// .next()
2403// .await;
2404// let completions = completions.await.unwrap();
2405// let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
2406// assert_eq!(completions.len(), 1);
2407// assert_eq!(completions[0].new_text, "component");
2408// assert_eq!(
2409// completions[0].old_range.to_offset(&snapshot),
2410// text.len() - 4..text.len() - 1
2411// );
2412// }
2413
2414// #[gpui::test]
2415// async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
2416// init_test(cx);
2417
2418// let mut language = Language::new(
2419// LanguageConfig {
2420// name: "TypeScript".into(),
2421// path_suffixes: vec!["ts".to_string()],
2422// ..Default::default()
2423// },
2424// Some(tree_sitter_typescript::language_typescript()),
2425// );
2426// let mut fake_language_servers = language
2427// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2428// capabilities: lsp::ServerCapabilities {
2429// completion_provider: Some(lsp::CompletionOptions {
2430// trigger_characters: Some(vec![":".to_string()]),
2431// ..Default::default()
2432// }),
2433// ..Default::default()
2434// },
2435// ..Default::default()
2436// }))
2437// .await;
2438
2439// let fs = FakeFs::new(cx.background());
2440// fs.insert_tree(
2441// "/dir",
2442// json!({
2443// "a.ts": "",
2444// }),
2445// )
2446// .await;
2447
2448// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
2449// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
2450// let buffer = project
2451// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
2452// .await
2453// .unwrap();
2454
2455// let fake_server = fake_language_servers.next().await.unwrap();
2456
2457// let text = "let a = b.fqn";
2458// buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
2459// let completions = project.update(cx, |project, cx| {
2460// project.completions(&buffer, text.len(), cx)
2461// });
2462
2463// fake_server
2464// .handle_request::<lsp2::request::Completion, _, _>(|_, _| async move {
2465// Ok(Some(lsp2::CompletionResponse::Array(vec![
2466// lsp2::CompletionItem {
2467// label: "fullyQualifiedName?".into(),
2468// insert_text: Some("fully\rQualified\r\nName".into()),
2469// ..Default::default()
2470// },
2471// ])))
2472// })
2473// .next()
2474// .await;
2475// let completions = completions.await.unwrap();
2476// assert_eq!(completions.len(), 1);
2477// assert_eq!(completions[0].new_text, "fully\nQualified\nName");
2478// }
2479
2480// #[gpui::test(iterations = 10)]
2481// async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
2482// init_test(cx);
2483
2484// let mut language = Language::new(
2485// LanguageConfig {
2486// name: "TypeScript".into(),
2487// path_suffixes: vec!["ts".to_string()],
2488// ..Default::default()
2489// },
2490// None,
2491// );
2492// let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2493
2494// let fs = FakeFs::new(cx.background());
2495// fs.insert_tree(
2496// "/dir",
2497// json!({
2498// "a.ts": "a",
2499// }),
2500// )
2501// .await;
2502
2503// let project = Project::test(fs, ["/dir".as_ref()], cx).await;
2504// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
2505// let buffer = project
2506// .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
2507// .await
2508// .unwrap();
2509
2510// let fake_server = fake_language_servers.next().await.unwrap();
2511
2512// // Language server returns code actions that contain commands, and not edits.
2513// let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx));
2514// fake_server
2515// .handle_request::<lsp2::request::CodeActionRequest, _, _>(|_, _| async move {
2516// Ok(Some(vec![
2517// lsp2::CodeActionOrCommand::CodeAction(lsp2::CodeAction {
2518// title: "The code action".into(),
2519// command: Some(lsp::Command {
2520// title: "The command".into(),
2521// command: "_the/command".into(),
2522// arguments: Some(vec![json!("the-argument")]),
2523// }),
2524// ..Default::default()
2525// }),
2526// lsp2::CodeActionOrCommand::CodeAction(lsp2::CodeAction {
2527// title: "two".into(),
2528// ..Default::default()
2529// }),
2530// ]))
2531// })
2532// .next()
2533// .await;
2534
2535// let action = actions.await.unwrap()[0].clone();
2536// let apply = project.update(cx, |project, cx| {
2537// project.apply_code_action(buffer.clone(), action, true, cx)
2538// });
2539
2540// // Resolving the code action does not populate its edits. In absence of
2541// // edits, we must execute the given command.
2542// fake_server.handle_request::<lsp2::request::CodeActionResolveRequest, _, _>(
2543// |action, _| async move { Ok(action) },
2544// );
2545
2546// // While executing the command, the language server sends the editor
2547// // a `workspaceEdit` request.
2548// fake_server
2549// .handle_request::<lsp2::request::ExecuteCommand, _, _>({
2550// let fake = fake_server.clone();
2551// move |params, _| {
2552// assert_eq!(params.command, "_the/command");
2553// let fake = fake.clone();
2554// async move {
2555// fake.server
2556// .request::<lsp2::request::ApplyWorkspaceEdit>(
2557// lsp2::ApplyWorkspaceEditParams {
2558// label: None,
2559// edit: lsp::WorkspaceEdit {
2560// changes: Some(
2561// [(
2562// lsp2::Url::from_file_path("/dir/a.ts").unwrap(),
2563// vec![lsp2::TextEdit {
2564// range: lsp2::Range::new(
2565// lsp2::Position::new(0, 0),
2566// lsp2::Position::new(0, 0),
2567// ),
2568// new_text: "X".into(),
2569// }],
2570// )]
2571// .into_iter()
2572// .collect(),
2573// ),
2574// ..Default::default()
2575// },
2576// },
2577// )
2578// .await
2579// .unwrap();
2580// Ok(Some(json!(null)))
2581// }
2582// }
2583// })
2584// .next()
2585// .await;
2586
2587// // Applying the code action returns a project transaction containing the edits
2588// // sent by the language server in its `workspaceEdit` request.
2589// let transaction = apply.await.unwrap();
2590// assert!(transaction.0.contains_key(&buffer));
2591// buffer.update(cx, |buffer, cx| {
2592// assert_eq!(buffer.text(), "Xa");
2593// buffer.undo(cx);
2594// assert_eq!(buffer.text(), "a");
2595// });
2596// }
2597
2598// #[gpui::test(iterations = 10)]
2599// async fn test_save_file(cx: &mut gpui::TestAppContext) {
2600// init_test(cx);
2601
2602// let fs = FakeFs::new(cx.background());
2603// fs.insert_tree(
2604// "/dir",
2605// json!({
2606// "file1": "the old contents",
2607// }),
2608// )
2609// .await;
2610
2611// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2612// let buffer = project
2613// .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2614// .await
2615// .unwrap();
2616// buffer.update(cx, |buffer, cx| {
2617// assert_eq!(buffer.text(), "the old contents");
2618// buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
2619// });
2620
2621// project
2622// .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
2623// .await
2624// .unwrap();
2625
2626// let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2627// assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
2628// }
2629
2630// #[gpui::test]
2631// async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
2632// init_test(cx);
2633
2634// let fs = FakeFs::new(cx.background());
2635// fs.insert_tree(
2636// "/dir",
2637// json!({
2638// "file1": "the old contents",
2639// }),
2640// )
2641// .await;
2642
2643// let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await;
2644// let buffer = project
2645// .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2646// .await
2647// .unwrap();
2648// buffer.update(cx, |buffer, cx| {
2649// buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
2650// });
2651
2652// project
2653// .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
2654// .await
2655// .unwrap();
2656
2657// let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2658// assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
2659// }
2660
2661// #[gpui::test]
2662// async fn test_save_as(cx: &mut gpui::TestAppContext) {
2663// init_test(cx);
2664
2665// let fs = FakeFs::new(cx.background());
2666// fs.insert_tree("/dir", json!({})).await;
2667
2668// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2669
2670// let languages = project.read_with(cx, |project, _| project.languages().clone());
2671// languages.register(
2672// "/some/path",
2673// LanguageConfig {
2674// name: "Rust".into(),
2675// path_suffixes: vec!["rs".into()],
2676// ..Default::default()
2677// },
2678// tree_sitter_rust::language(),
2679// vec![],
2680// |_| Default::default(),
2681// );
2682
2683// let buffer = project.update(cx, |project, cx| {
2684// project.create_buffer("", None, cx).unwrap()
2685// });
2686// buffer.update(cx, |buffer, cx| {
2687// buffer.edit([(0..0, "abc")], None, cx);
2688// assert!(buffer.is_dirty());
2689// assert!(!buffer.has_conflict());
2690// assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
2691// });
2692// project
2693// .update(cx, |project, cx| {
2694// project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
2695// })
2696// .await
2697// .unwrap();
2698// assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
2699
2700// cx.foreground().run_until_parked();
2701// buffer.read_with(cx, |buffer, cx| {
2702// assert_eq!(
2703// buffer.file().unwrap().full_path(cx),
2704// Path::new("dir/file1.rs")
2705// );
2706// assert!(!buffer.is_dirty());
2707// assert!(!buffer.has_conflict());
2708// assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
2709// });
2710
2711// let opened_buffer = project
2712// .update(cx, |project, cx| {
2713// project.open_local_buffer("/dir/file1.rs", cx)
2714// })
2715// .await
2716// .unwrap();
2717// assert_eq!(opened_buffer, buffer);
2718// }
2719
2720// #[gpui::test(retries = 5)]
2721// async fn test_rescan_and_remote_updates(
2722// deterministic: Arc<Deterministic>,
2723// cx: &mut gpui::TestAppContext,
2724// ) {
2725// init_test(cx);
2726// cx.foreground().allow_parking();
2727
2728// let dir = temp_tree(json!({
2729// "a": {
2730// "file1": "",
2731// "file2": "",
2732// "file3": "",
2733// },
2734// "b": {
2735// "c": {
2736// "file4": "",
2737// "file5": "",
2738// }
2739// }
2740// }));
2741
2742// let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await;
2743// let rpc = project.read_with(cx, |p, _| p.client.clone());
2744
2745// let buffer_for_path = |path: &'static str, cx: &mut gpui2::TestAppContext| {
2746// let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx));
2747// async move { buffer.await.unwrap() }
2748// };
2749// let id_for_path = |path: &'static str, cx: &gpui2::TestAppContext| {
2750// project.read_with(cx, |project, cx| {
2751// let tree = project.worktrees(cx).next().unwrap();
2752// tree.read(cx)
2753// .entry_for_path(path)
2754// .unwrap_or_else(|| panic!("no entry for path {}", path))
2755// .id
2756// })
2757// };
2758
2759// let buffer2 = buffer_for_path("a/file2", cx).await;
2760// let buffer3 = buffer_for_path("a/file3", cx).await;
2761// let buffer4 = buffer_for_path("b/c/file4", cx).await;
2762// let buffer5 = buffer_for_path("b/c/file5", cx).await;
2763
2764// let file2_id = id_for_path("a/file2", cx);
2765// let file3_id = id_for_path("a/file3", cx);
2766// let file4_id = id_for_path("b/c/file4", cx);
2767
2768// // Create a remote copy of this worktree.
2769// let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
2770
2771// let metadata = tree.read_with(cx, |tree, _| tree.as_local().unwrap().metadata_proto());
2772
2773// let updates = Arc::new(Mutex::new(Vec::new()));
2774// tree.update(cx, |tree, cx| {
2775// let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
2776// let updates = updates.clone();
2777// move |update| {
2778// updates.lock().push(update);
2779// async { true }
2780// }
2781// });
2782// });
2783
2784// let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx));
2785// deterministic.run_until_parked();
2786
2787// cx.read(|cx| {
2788// assert!(!buffer2.read(cx).is_dirty());
2789// assert!(!buffer3.read(cx).is_dirty());
2790// assert!(!buffer4.read(cx).is_dirty());
2791// assert!(!buffer5.read(cx).is_dirty());
2792// });
2793
2794// // Rename and delete files and directories.
2795// tree.flush_fs_events(cx).await;
2796// std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
2797// std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
2798// std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
2799// std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
2800// tree.flush_fs_events(cx).await;
2801
2802// let expected_paths = vec![
2803// "a",
2804// "a/file1",
2805// "a/file2.new",
2806// "b",
2807// "d",
2808// "d/file3",
2809// "d/file4",
2810// ];
2811
2812// cx.read(|app| {
2813// assert_eq!(
2814// tree.read(app)
2815// .paths()
2816// .map(|p| p.to_str().unwrap())
2817// .collect::<Vec<_>>(),
2818// expected_paths
2819// );
2820
2821// assert_eq!(id_for_path("a/file2.new", cx), file2_id);
2822// assert_eq!(id_for_path("d/file3", cx), file3_id);
2823// assert_eq!(id_for_path("d/file4", cx), file4_id);
2824
2825// assert_eq!(
2826// buffer2.read(app).file().unwrap().path().as_ref(),
2827// Path::new("a/file2.new")
2828// );
2829// assert_eq!(
2830// buffer3.read(app).file().unwrap().path().as_ref(),
2831// Path::new("d/file3")
2832// );
2833// assert_eq!(
2834// buffer4.read(app).file().unwrap().path().as_ref(),
2835// Path::new("d/file4")
2836// );
2837// assert_eq!(
2838// buffer5.read(app).file().unwrap().path().as_ref(),
2839// Path::new("b/c/file5")
2840// );
2841
2842// assert!(!buffer2.read(app).file().unwrap().is_deleted());
2843// assert!(!buffer3.read(app).file().unwrap().is_deleted());
2844// assert!(!buffer4.read(app).file().unwrap().is_deleted());
2845// assert!(buffer5.read(app).file().unwrap().is_deleted());
2846// });
2847
2848// // Update the remote worktree. Check that it becomes consistent with the
2849// // local worktree.
2850// deterministic.run_until_parked();
2851// remote.update(cx, |remote, _| {
2852// for update in updates.lock().drain(..) {
2853// remote.as_remote_mut().unwrap().update_from_remote(update);
2854// }
2855// });
2856// deterministic.run_until_parked();
2857// remote.read_with(cx, |remote, _| {
2858// assert_eq!(
2859// remote
2860// .paths()
2861// .map(|p| p.to_str().unwrap())
2862// .collect::<Vec<_>>(),
2863// expected_paths
2864// );
2865// });
2866// }
2867
2868// #[gpui::test(iterations = 10)]
2869// async fn test_buffer_identity_across_renames(
2870// deterministic: Arc<Deterministic>,
2871// cx: &mut gpui::TestAppContext,
2872// ) {
2873// init_test(cx);
2874
2875// let fs = FakeFs::new(cx.background());
2876// fs.insert_tree(
2877// "/dir",
2878// json!({
2879// "a": {
2880// "file1": "",
2881// }
2882// }),
2883// )
2884// .await;
2885
2886// let project = Project::test(fs, [Path::new("/dir")], cx).await;
2887// let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
2888// let tree_id = tree.read_with(cx, |tree, _| tree.id());
2889
2890// let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
2891// project.read_with(cx, |project, cx| {
2892// let tree = project.worktrees(cx).next().unwrap();
2893// tree.read(cx)
2894// .entry_for_path(path)
2895// .unwrap_or_else(|| panic!("no entry for path {}", path))
2896// .id
2897// })
2898// };
2899
2900// let dir_id = id_for_path("a", cx);
2901// let file_id = id_for_path("a/file1", cx);
2902// let buffer = project
2903// .update(cx, |p, cx| p.open_buffer((tree_id, "a/file1"), cx))
2904// .await
2905// .unwrap();
2906// buffer.read_with(cx, |buffer, _| assert!(!buffer.is_dirty()));
2907
2908// project
2909// .update(cx, |project, cx| {
2910// project.rename_entry(dir_id, Path::new("b"), cx)
2911// })
2912// .unwrap()
2913// .await
2914// .unwrap();
2915// deterministic.run_until_parked();
2916// assert_eq!(id_for_path("b", cx), dir_id);
2917// assert_eq!(id_for_path("b/file1", cx), file_id);
2918// buffer.read_with(cx, |buffer, _| assert!(!buffer.is_dirty()));
2919// }
2920
2921// #[gpui2::test]
2922// async fn test_buffer_deduping(cx: &mut gpui2::TestAppContext) {
2923// init_test(cx);
2924
2925// let fs = FakeFs::new(cx.background());
2926// fs.insert_tree(
2927// "/dir",
2928// json!({
2929// "a.txt": "a-contents",
2930// "b.txt": "b-contents",
2931// }),
2932// )
2933// .await;
2934
2935// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2936
2937// // Spawn multiple tasks to open paths, repeating some paths.
2938// let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| {
2939// (
2940// p.open_local_buffer("/dir/a.txt", cx),
2941// p.open_local_buffer("/dir/b.txt", cx),
2942// p.open_local_buffer("/dir/a.txt", cx),
2943// )
2944// });
2945
2946// let buffer_a_1 = buffer_a_1.await.unwrap();
2947// let buffer_a_2 = buffer_a_2.await.unwrap();
2948// let buffer_b = buffer_b.await.unwrap();
2949// assert_eq!(buffer_a_1.read_with(cx, |b, _| b.text()), "a-contents");
2950// assert_eq!(buffer_b.read_with(cx, |b, _| b.text()), "b-contents");
2951
2952// // There is only one buffer per path.
2953// let buffer_a_id = buffer_a_1.id();
2954// assert_eq!(buffer_a_2.id(), buffer_a_id);
2955
2956// // Open the same path again while it is still open.
2957// drop(buffer_a_1);
2958// let buffer_a_3 = project
2959// .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx))
2960// .await
2961// .unwrap();
2962
2963// // There's still only one buffer per path.
2964// assert_eq!(buffer_a_3.id(), buffer_a_id);
2965// }
2966
2967// #[gpui2::test]
2968// async fn test_buffer_is_dirty(cx: &mut gpui2::TestAppContext) {
2969// init_test(cx);
2970
2971// let fs = FakeFs::new(cx.background());
2972// fs.insert_tree(
2973// "/dir",
2974// json!({
2975// "file1": "abc",
2976// "file2": "def",
2977// "file3": "ghi",
2978// }),
2979// )
2980// .await;
2981
2982// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2983
2984// let buffer1 = project
2985// .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2986// .await
2987// .unwrap();
2988// let events = Rc::new(RefCell::new(Vec::new()));
2989
2990// // initially, the buffer isn't dirty.
2991// buffer1.update(cx, |buffer, cx| {
2992// cx.subscribe(&buffer1, {
2993// let events = events.clone();
2994// move |_, _, event, _| match event {
2995// BufferEvent::Operation(_) => {}
2996// _ => events.borrow_mut().push(event.clone()),
2997// }
2998// })
2999// .detach();
3000
3001// assert!(!buffer.is_dirty());
3002// assert!(events.borrow().is_empty());
3003
3004// buffer.edit([(1..2, "")], None, cx);
3005// });
3006
3007// // after the first edit, the buffer is dirty, and emits a dirtied event.
3008// buffer1.update(cx, |buffer, cx| {
3009// assert!(buffer.text() == "ac");
3010// assert!(buffer.is_dirty());
3011// assert_eq!(
3012// *events.borrow(),
3013// &[language2::Event::Edited, language2::Event::DirtyChanged]
3014// );
3015// events.borrow_mut().clear();
3016// buffer.did_save(
3017// buffer.version(),
3018// buffer.as_rope().fingerprint(),
3019// buffer.file().unwrap().mtime(),
3020// cx,
3021// );
3022// });
3023
3024// // after saving, the buffer is not dirty, and emits a saved event.
3025// buffer1.update(cx, |buffer, cx| {
3026// assert!(!buffer.is_dirty());
3027// assert_eq!(*events.borrow(), &[language2::Event::Saved]);
3028// events.borrow_mut().clear();
3029
3030// buffer.edit([(1..1, "B")], None, cx);
3031// buffer.edit([(2..2, "D")], None, cx);
3032// });
3033
3034// // after editing again, the buffer is dirty, and emits another dirty event.
3035// buffer1.update(cx, |buffer, cx| {
3036// assert!(buffer.text() == "aBDc");
3037// assert!(buffer.is_dirty());
3038// assert_eq!(
3039// *events.borrow(),
3040// &[
3041// language2::Event::Edited,
3042// language2::Event::DirtyChanged,
3043// language2::Event::Edited,
3044// ],
3045// );
3046// events.borrow_mut().clear();
3047
3048// // After restoring the buffer to its previously-saved state,
3049// // the buffer is not considered dirty anymore.
3050// buffer.edit([(1..3, "")], None, cx);
3051// assert!(buffer.text() == "ac");
3052// assert!(!buffer.is_dirty());
3053// });
3054
3055// assert_eq!(
3056// *events.borrow(),
3057// &[language2::Event::Edited, language2::Event::DirtyChanged]
3058// );
3059
3060// // When a file is deleted, the buffer is considered dirty.
3061// let events = Rc::new(RefCell::new(Vec::new()));
3062// let buffer2 = project
3063// .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
3064// .await
3065// .unwrap();
3066// buffer2.update(cx, |_, cx| {
3067// cx.subscribe(&buffer2, {
3068// let events = events.clone();
3069// move |_, _, event, _| events.borrow_mut().push(event.clone())
3070// })
3071// .detach();
3072// });
3073
3074// fs.remove_file("/dir/file2".as_ref(), Default::default())
3075// .await
3076// .unwrap();
3077// cx.foreground().run_until_parked();
3078// buffer2.read_with(cx, |buffer, _| assert!(buffer.is_dirty()));
3079// assert_eq!(
3080// *events.borrow(),
3081// &[
3082// language2::Event::DirtyChanged,
3083// language2::Event::FileHandleChanged
3084// ]
3085// );
3086
3087// // When a file is already dirty when deleted, we don't emit a Dirtied event.
3088// let events = Rc::new(RefCell::new(Vec::new()));
3089// let buffer3 = project
3090// .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx))
3091// .await
3092// .unwrap();
3093// buffer3.update(cx, |_, cx| {
3094// cx.subscribe(&buffer3, {
3095// let events = events.clone();
3096// move |_, _, event, _| events.borrow_mut().push(event.clone())
3097// })
3098// .detach();
3099// });
3100
3101// buffer3.update(cx, |buffer, cx| {
3102// buffer.edit([(0..0, "x")], None, cx);
3103// });
3104// events.borrow_mut().clear();
3105// fs.remove_file("/dir/file3".as_ref(), Default::default())
3106// .await
3107// .unwrap();
3108// cx.foreground().run_until_parked();
3109// assert_eq!(*events.borrow(), &[language2::Event::FileHandleChanged]);
3110// cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
3111// }
3112
3113// #[gpui::test]
3114// async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
3115// init_test(cx);
3116
3117// let initial_contents = "aaa\nbbbbb\nc\n";
3118// let fs = FakeFs::new(cx.background());
3119// fs.insert_tree(
3120// "/dir",
3121// json!({
3122// "the-file": initial_contents,
3123// }),
3124// )
3125// .await;
3126// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3127// let buffer = project
3128// .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx))
3129// .await
3130// .unwrap();
3131
3132// let anchors = (0..3)
3133// .map(|row| buffer.read_with(cx, |b, _| b.anchor_before(Point::new(row, 1))))
3134// .collect::<Vec<_>>();
3135
3136// // Change the file on disk, adding two new lines of text, and removing
3137// // one line.
3138// buffer.read_with(cx, |buffer, _| {
3139// assert!(!buffer.is_dirty());
3140// assert!(!buffer.has_conflict());
3141// });
3142// let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
3143// fs.save(
3144// "/dir/the-file".as_ref(),
3145// &new_contents.into(),
3146// LineEnding::Unix,
3147// )
3148// .await
3149// .unwrap();
3150
3151// // Because the buffer was not modified, it is reloaded from disk. Its
3152// // contents are edited according to the diff between the old and new
3153// // file contents.
3154// cx.foreground().run_until_parked();
3155// buffer.update(cx, |buffer, _| {
3156// assert_eq!(buffer.text(), new_contents);
3157// assert!(!buffer.is_dirty());
3158// assert!(!buffer.has_conflict());
3159
3160// let anchor_positions = anchors
3161// .iter()
3162// .map(|anchor| anchor.to_point(&*buffer))
3163// .collect::<Vec<_>>();
3164// assert_eq!(
3165// anchor_positions,
3166// [Point::new(1, 1), Point::new(3, 1), Point::new(3, 5)]
3167// );
3168// });
3169
3170// // Modify the buffer
3171// buffer.update(cx, |buffer, cx| {
3172// buffer.edit([(0..0, " ")], None, cx);
3173// assert!(buffer.is_dirty());
3174// assert!(!buffer.has_conflict());
3175// });
3176
3177// // Change the file on disk again, adding blank lines to the beginning.
3178// fs.save(
3179// "/dir/the-file".as_ref(),
3180// &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(),
3181// LineEnding::Unix,
3182// )
3183// .await
3184// .unwrap();
3185
3186// // Because the buffer is modified, it doesn't reload from disk, but is
3187// // marked as having a conflict.
3188// cx.foreground().run_until_parked();
3189// buffer.read_with(cx, |buffer, _| {
3190// assert!(buffer.has_conflict());
3191// });
3192// }
3193
3194// #[gpui::test]
3195// async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
3196// init_test(cx);
3197
3198// let fs = FakeFs::new(cx.background());
3199// fs.insert_tree(
3200// "/dir",
3201// json!({
3202// "file1": "a\nb\nc\n",
3203// "file2": "one\r\ntwo\r\nthree\r\n",
3204// }),
3205// )
3206// .await;
3207
3208// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3209// let buffer1 = project
3210// .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
3211// .await
3212// .unwrap();
3213// let buffer2 = project
3214// .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
3215// .await
3216// .unwrap();
3217
3218// buffer1.read_with(cx, |buffer, _| {
3219// assert_eq!(buffer.text(), "a\nb\nc\n");
3220// assert_eq!(buffer.line_ending(), LineEnding::Unix);
3221// });
3222// buffer2.read_with(cx, |buffer, _| {
3223// assert_eq!(buffer.text(), "one\ntwo\nthree\n");
3224// assert_eq!(buffer.line_ending(), LineEnding::Windows);
3225// });
3226
3227// // Change a file's line endings on disk from unix to windows. The buffer's
3228// // state updates correctly.
3229// fs.save(
3230// "/dir/file1".as_ref(),
3231// &"aaa\nb\nc\n".into(),
3232// LineEnding::Windows,
3233// )
3234// .await
3235// .unwrap();
3236// cx.foreground().run_until_parked();
3237// buffer1.read_with(cx, |buffer, _| {
3238// assert_eq!(buffer.text(), "aaa\nb\nc\n");
3239// assert_eq!(buffer.line_ending(), LineEnding::Windows);
3240// });
3241
3242// // Save a file with windows line endings. The file is written correctly.
3243// buffer2.update(cx, |buffer, cx| {
3244// buffer.set_text("one\ntwo\nthree\nfour\n", cx);
3245// });
3246// project
3247// .update(cx, |project, cx| project.save_buffer(buffer2, cx))
3248// .await
3249// .unwrap();
3250// assert_eq!(
3251// fs.load("/dir/file2".as_ref()).await.unwrap(),
3252// "one\r\ntwo\r\nthree\r\nfour\r\n",
3253// );
3254// }
3255
3256// #[gpui::test]
3257// async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
3258// init_test(cx);
3259
3260// let fs = FakeFs::new(cx.background());
3261// fs.insert_tree(
3262// "/the-dir",
3263// json!({
3264// "a.rs": "
3265// fn foo(mut v: Vec<usize>) {
3266// for x in &v {
3267// v.push(1);
3268// }
3269// }
3270// "
3271// .unindent(),
3272// }),
3273// )
3274// .await;
3275
3276// let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await;
3277// let buffer = project
3278// .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx))
3279// .await
3280// .unwrap();
3281
3282// let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
3283// let message = lsp::PublishDiagnosticsParams {
3284// uri: buffer_uri.clone(),
3285// diagnostics: vec![
3286// lsp2::Diagnostic {
3287// range: lsp2::Range::new(lsp2::Position::new(1, 8), lsp2::Position::new(1, 9)),
3288// severity: Some(DiagnosticSeverity::WARNING),
3289// message: "error 1".to_string(),
3290// related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3291// location: lsp::Location {
3292// uri: buffer_uri.clone(),
3293// range: lsp2::Range::new(
3294// lsp2::Position::new(1, 8),
3295// lsp2::Position::new(1, 9),
3296// ),
3297// },
3298// message: "error 1 hint 1".to_string(),
3299// }]),
3300// ..Default::default()
3301// },
3302// lsp2::Diagnostic {
3303// range: lsp2::Range::new(lsp2::Position::new(1, 8), lsp2::Position::new(1, 9)),
3304// severity: Some(DiagnosticSeverity::HINT),
3305// message: "error 1 hint 1".to_string(),
3306// related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3307// location: lsp::Location {
3308// uri: buffer_uri.clone(),
3309// range: lsp2::Range::new(
3310// lsp2::Position::new(1, 8),
3311// lsp2::Position::new(1, 9),
3312// ),
3313// },
3314// message: "original diagnostic".to_string(),
3315// }]),
3316// ..Default::default()
3317// },
3318// lsp2::Diagnostic {
3319// range: lsp2::Range::new(lsp2::Position::new(2, 8), lsp2::Position::new(2, 17)),
3320// severity: Some(DiagnosticSeverity::ERROR),
3321// message: "error 2".to_string(),
3322// related_information: Some(vec![
3323// lsp::DiagnosticRelatedInformation {
3324// location: lsp::Location {
3325// uri: buffer_uri.clone(),
3326// range: lsp2::Range::new(
3327// lsp2::Position::new(1, 13),
3328// lsp2::Position::new(1, 15),
3329// ),
3330// },
3331// message: "error 2 hint 1".to_string(),
3332// },
3333// lsp::DiagnosticRelatedInformation {
3334// location: lsp::Location {
3335// uri: buffer_uri.clone(),
3336// range: lsp2::Range::new(
3337// lsp2::Position::new(1, 13),
3338// lsp2::Position::new(1, 15),
3339// ),
3340// },
3341// message: "error 2 hint 2".to_string(),
3342// },
3343// ]),
3344// ..Default::default()
3345// },
3346// lsp2::Diagnostic {
3347// range: lsp2::Range::new(lsp2::Position::new(1, 13), lsp2::Position::new(1, 15)),
3348// severity: Some(DiagnosticSeverity::HINT),
3349// message: "error 2 hint 1".to_string(),
3350// related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3351// location: lsp::Location {
3352// uri: buffer_uri.clone(),
3353// range: lsp2::Range::new(
3354// lsp2::Position::new(2, 8),
3355// lsp2::Position::new(2, 17),
3356// ),
3357// },
3358// message: "original diagnostic".to_string(),
3359// }]),
3360// ..Default::default()
3361// },
3362// lsp2::Diagnostic {
3363// range: lsp2::Range::new(lsp2::Position::new(1, 13), lsp2::Position::new(1, 15)),
3364// severity: Some(DiagnosticSeverity::HINT),
3365// message: "error 2 hint 2".to_string(),
3366// related_information: Some(vec![lsp::DiagnosticRelatedInformation {
3367// location: lsp::Location {
3368// uri: buffer_uri,
3369// range: lsp2::Range::new(
3370// lsp2::Position::new(2, 8),
3371// lsp2::Position::new(2, 17),
3372// ),
3373// },
3374// message: "original diagnostic".to_string(),
3375// }]),
3376// ..Default::default()
3377// },
3378// ],
3379// version: None,
3380// };
3381
3382// project
3383// .update(cx, |p, cx| {
3384// p.update_diagnostics(LanguageServerId(0), message, &[], cx)
3385// })
3386// .unwrap();
3387// let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot());
3388
3389// assert_eq!(
3390// buffer
3391// .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
3392// .collect::<Vec<_>>(),
3393// &[
3394// DiagnosticEntry {
3395// range: Point::new(1, 8)..Point::new(1, 9),
3396// diagnostic: Diagnostic {
3397// severity: DiagnosticSeverity::WARNING,
3398// message: "error 1".to_string(),
3399// group_id: 1,
3400// is_primary: true,
3401// ..Default::default()
3402// }
3403// },
3404// DiagnosticEntry {
3405// range: Point::new(1, 8)..Point::new(1, 9),
3406// diagnostic: Diagnostic {
3407// severity: DiagnosticSeverity::HINT,
3408// message: "error 1 hint 1".to_string(),
3409// group_id: 1,
3410// is_primary: false,
3411// ..Default::default()
3412// }
3413// },
3414// DiagnosticEntry {
3415// range: Point::new(1, 13)..Point::new(1, 15),
3416// diagnostic: Diagnostic {
3417// severity: DiagnosticSeverity::HINT,
3418// message: "error 2 hint 1".to_string(),
3419// group_id: 0,
3420// is_primary: false,
3421// ..Default::default()
3422// }
3423// },
3424// DiagnosticEntry {
3425// range: Point::new(1, 13)..Point::new(1, 15),
3426// diagnostic: Diagnostic {
3427// severity: DiagnosticSeverity::HINT,
3428// message: "error 2 hint 2".to_string(),
3429// group_id: 0,
3430// is_primary: false,
3431// ..Default::default()
3432// }
3433// },
3434// DiagnosticEntry {
3435// range: Point::new(2, 8)..Point::new(2, 17),
3436// diagnostic: Diagnostic {
3437// severity: DiagnosticSeverity::ERROR,
3438// message: "error 2".to_string(),
3439// group_id: 0,
3440// is_primary: true,
3441// ..Default::default()
3442// }
3443// }
3444// ]
3445// );
3446
3447// assert_eq!(
3448// buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
3449// &[
3450// DiagnosticEntry {
3451// range: Point::new(1, 13)..Point::new(1, 15),
3452// diagnostic: Diagnostic {
3453// severity: DiagnosticSeverity::HINT,
3454// message: "error 2 hint 1".to_string(),
3455// group_id: 0,
3456// is_primary: false,
3457// ..Default::default()
3458// }
3459// },
3460// DiagnosticEntry {
3461// range: Point::new(1, 13)..Point::new(1, 15),
3462// diagnostic: Diagnostic {
3463// severity: DiagnosticSeverity::HINT,
3464// message: "error 2 hint 2".to_string(),
3465// group_id: 0,
3466// is_primary: false,
3467// ..Default::default()
3468// }
3469// },
3470// DiagnosticEntry {
3471// range: Point::new(2, 8)..Point::new(2, 17),
3472// diagnostic: Diagnostic {
3473// severity: DiagnosticSeverity::ERROR,
3474// message: "error 2".to_string(),
3475// group_id: 0,
3476// is_primary: true,
3477// ..Default::default()
3478// }
3479// }
3480// ]
3481// );
3482
3483// assert_eq!(
3484// buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
3485// &[
3486// DiagnosticEntry {
3487// range: Point::new(1, 8)..Point::new(1, 9),
3488// diagnostic: Diagnostic {
3489// severity: DiagnosticSeverity::WARNING,
3490// message: "error 1".to_string(),
3491// group_id: 1,
3492// is_primary: true,
3493// ..Default::default()
3494// }
3495// },
3496// DiagnosticEntry {
3497// range: Point::new(1, 8)..Point::new(1, 9),
3498// diagnostic: Diagnostic {
3499// severity: DiagnosticSeverity::HINT,
3500// message: "error 1 hint 1".to_string(),
3501// group_id: 1,
3502// is_primary: false,
3503// ..Default::default()
3504// }
3505// },
3506// ]
3507// );
3508// }
3509
3510// #[gpui::test]
3511// async fn test_rename(cx: &mut gpui::TestAppContext) {
3512// init_test(cx);
3513
3514// let mut language = Language::new(
3515// LanguageConfig {
3516// name: "Rust".into(),
3517// path_suffixes: vec!["rs".to_string()],
3518// ..Default::default()
3519// },
3520// Some(tree_sitter_rust::language()),
3521// );
3522// let mut fake_servers = language
3523// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3524// capabilities: lsp2::ServerCapabilities {
3525// rename_provider: Some(lsp2::OneOf::Right(lsp2::RenameOptions {
3526// prepare_provider: Some(true),
3527// work_done_progress_options: Default::default(),
3528// })),
3529// ..Default::default()
3530// },
3531// ..Default::default()
3532// }))
3533// .await;
3534
3535// let fs = FakeFs::new(cx.background());
3536// fs.insert_tree(
3537// "/dir",
3538// json!({
3539// "one.rs": "const ONE: usize = 1;",
3540// "two.rs": "const TWO: usize = one::ONE + one::ONE;"
3541// }),
3542// )
3543// .await;
3544
3545// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3546// project.update(cx, |project, _| project.languages.add(Arc::new(language)));
3547// let buffer = project
3548// .update(cx, |project, cx| {
3549// project.open_local_buffer("/dir/one.rs", cx)
3550// })
3551// .await
3552// .unwrap();
3553
3554// let fake_server = fake_servers.next().await.unwrap();
3555
3556// let response = project.update(cx, |project, cx| {
3557// project.prepare_rename(buffer.clone(), 7, cx)
3558// });
3559// fake_server
3560// .handle_request::<lsp2::request::PrepareRenameRequest, _, _>(|params, _| async move {
3561// assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
3562// assert_eq!(params.position, lsp2::Position::new(0, 7));
3563// Ok(Some(lsp2::PrepareRenameResponse::Range(lsp2::Range::new(
3564// lsp2::Position::new(0, 6),
3565// lsp2::Position::new(0, 9),
3566// ))))
3567// })
3568// .next()
3569// .await
3570// .unwrap();
3571// let range = response.await.unwrap().unwrap();
3572// let range = buffer.read_with(cx, |buffer, _| range.to_offset(buffer));
3573// assert_eq!(range, 6..9);
3574
3575// let response = project.update(cx, |project, cx| {
3576// project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
3577// });
3578// fake_server
3579// .handle_request::<lsp2::request::Rename, _, _>(|params, _| async move {
3580// assert_eq!(
3581// params.text_document_position.text_document.uri.as_str(),
3582// "file:///dir/one.rs"
3583// );
3584// assert_eq!(
3585// params.text_document_position.position,
3586// lsp2::Position::new(0, 7)
3587// );
3588// assert_eq!(params.new_name, "THREE");
3589// Ok(Some(lsp::WorkspaceEdit {
3590// changes: Some(
3591// [
3592// (
3593// lsp2::Url::from_file_path("/dir/one.rs").unwrap(),
3594// vec![lsp2::TextEdit::new(
3595// lsp2::Range::new(
3596// lsp2::Position::new(0, 6),
3597// lsp2::Position::new(0, 9),
3598// ),
3599// "THREE".to_string(),
3600// )],
3601// ),
3602// (
3603// lsp2::Url::from_file_path("/dir/two.rs").unwrap(),
3604// vec![
3605// lsp2::TextEdit::new(
3606// lsp2::Range::new(
3607// lsp2::Position::new(0, 24),
3608// lsp2::Position::new(0, 27),
3609// ),
3610// "THREE".to_string(),
3611// ),
3612// lsp2::TextEdit::new(
3613// lsp2::Range::new(
3614// lsp2::Position::new(0, 35),
3615// lsp2::Position::new(0, 38),
3616// ),
3617// "THREE".to_string(),
3618// ),
3619// ],
3620// ),
3621// ]
3622// .into_iter()
3623// .collect(),
3624// ),
3625// ..Default::default()
3626// }))
3627// })
3628// .next()
3629// .await
3630// .unwrap();
3631// let mut transaction = response.await.unwrap().0;
3632// assert_eq!(transaction.len(), 2);
3633// assert_eq!(
3634// transaction
3635// .remove_entry(&buffer)
3636// .unwrap()
3637// .0
3638// .read_with(cx, |buffer, _| buffer.text()),
3639// "const THREE: usize = 1;"
3640// );
3641// assert_eq!(
3642// transaction
3643// .into_keys()
3644// .next()
3645// .unwrap()
3646// .read_with(cx, |buffer, _| buffer.text()),
3647// "const TWO: usize = one::THREE + one::THREE;"
3648// );
3649// }
3650
3651// #[gpui::test]
3652// async fn test_search(cx: &mut gpui::TestAppContext) {
3653// init_test(cx);
3654
3655// let fs = FakeFs::new(cx.background());
3656// fs.insert_tree(
3657// "/dir",
3658// json!({
3659// "one.rs": "const ONE: usize = 1;",
3660// "two.rs": "const TWO: usize = one::ONE + one::ONE;",
3661// "three.rs": "const THREE: usize = one::ONE + two::TWO;",
3662// "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
3663// }),
3664// )
3665// .await;
3666// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3667// assert_eq!(
3668// search(
3669// &project,
3670// SearchQuery::text("TWO", false, true, Vec::new(), Vec::new()).unwrap(),
3671// cx
3672// )
3673// .await
3674// .unwrap(),
3675// HashMap::from_iter([
3676// ("two.rs".to_string(), vec![6..9]),
3677// ("three.rs".to_string(), vec![37..40])
3678// ])
3679// );
3680
3681// let buffer_4 = project
3682// .update(cx, |project, cx| {
3683// project.open_local_buffer("/dir/four.rs", cx)
3684// })
3685// .await
3686// .unwrap();
3687// buffer_4.update(cx, |buffer, cx| {
3688// let text = "two::TWO";
3689// buffer.edit([(20..28, text), (31..43, text)], None, cx);
3690// });
3691
3692// assert_eq!(
3693// search(
3694// &project,
3695// SearchQuery::text("TWO", false, true, Vec::new(), Vec::new()).unwrap(),
3696// cx
3697// )
3698// .await
3699// .unwrap(),
3700// HashMap::from_iter([
3701// ("two.rs".to_string(), vec![6..9]),
3702// ("three.rs".to_string(), vec![37..40]),
3703// ("four.rs".to_string(), vec![25..28, 36..39])
3704// ])
3705// );
3706// }
3707
3708// #[gpui::test]
3709// async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
3710// init_test(cx);
3711
3712// let search_query = "file";
3713
3714// let fs = FakeFs::new(cx.background());
3715// fs.insert_tree(
3716// "/dir",
3717// json!({
3718// "one.rs": r#"// Rust file one"#,
3719// "one.ts": r#"// TypeScript file one"#,
3720// "two.rs": r#"// Rust file two"#,
3721// "two.ts": r#"// TypeScript file two"#,
3722// }),
3723// )
3724// .await;
3725// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3726
3727// assert!(
3728// search(
3729// &project,
3730// SearchQuery::text(
3731// search_query,
3732// false,
3733// true,
3734// vec![PathMatcher::new("*.odd").unwrap()],
3735// Vec::new()
3736// )
3737// .unwrap(),
3738// cx
3739// )
3740// .await
3741// .unwrap()
3742// .is_empty(),
3743// "If no inclusions match, no files should be returned"
3744// );
3745
3746// assert_eq!(
3747// search(
3748// &project,
3749// SearchQuery::text(
3750// search_query,
3751// false,
3752// true,
3753// vec![PathMatcher::new("*.rs").unwrap()],
3754// Vec::new()
3755// )
3756// .unwrap(),
3757// cx
3758// )
3759// .await
3760// .unwrap(),
3761// HashMap::from_iter([
3762// ("one.rs".to_string(), vec![8..12]),
3763// ("two.rs".to_string(), vec![8..12]),
3764// ]),
3765// "Rust only search should give only Rust files"
3766// );
3767
3768// assert_eq!(
3769// search(
3770// &project,
3771// SearchQuery::text(
3772// search_query,
3773// false,
3774// true,
3775// vec![
3776// PathMatcher::new("*.ts").unwrap(),
3777// PathMatcher::new("*.odd").unwrap(),
3778// ],
3779// Vec::new()
3780// ).unwrap(),
3781// cx
3782// )
3783// .await
3784// .unwrap(),
3785// HashMap::from_iter([
3786// ("one.ts".to_string(), vec![14..18]),
3787// ("two.ts".to_string(), vec![14..18]),
3788// ]),
3789// "TypeScript only search should give only TypeScript files, even if other inclusions don't match anything"
3790// );
3791
3792// assert_eq!(
3793// search(
3794// &project,
3795// SearchQuery::text(
3796// search_query,
3797// false,
3798// true,
3799// vec![
3800// PathMatcher::new("*.rs").unwrap(),
3801// PathMatcher::new("*.ts").unwrap(),
3802// PathMatcher::new("*.odd").unwrap(),
3803// ],
3804// Vec::new()
3805// ).unwrap(),
3806// cx
3807// )
3808// .await
3809// .unwrap(),
3810// HashMap::from_iter([
3811// ("one.rs".to_string(), vec![8..12]),
3812// ("one.ts".to_string(), vec![14..18]),
3813// ("two.rs".to_string(), vec![8..12]),
3814// ("two.ts".to_string(), vec![14..18]),
3815// ]),
3816// "Rust and typescript search should give both Rust and TypeScript files, even if other inclusions don't match anything"
3817// );
3818// }
3819
3820// #[gpui::test]
3821// async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
3822// init_test(cx);
3823
3824// let search_query = "file";
3825
3826// let fs = FakeFs::new(cx.background());
3827// fs.insert_tree(
3828// "/dir",
3829// json!({
3830// "one.rs": r#"// Rust file one"#,
3831// "one.ts": r#"// TypeScript file one"#,
3832// "two.rs": r#"// Rust file two"#,
3833// "two.ts": r#"// TypeScript file two"#,
3834// }),
3835// )
3836// .await;
3837// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3838
3839// assert_eq!(
3840// search(
3841// &project,
3842// SearchQuery::text(
3843// search_query,
3844// false,
3845// true,
3846// Vec::new(),
3847// vec![PathMatcher::new("*.odd").unwrap()],
3848// )
3849// .unwrap(),
3850// cx
3851// )
3852// .await
3853// .unwrap(),
3854// HashMap::from_iter([
3855// ("one.rs".to_string(), vec![8..12]),
3856// ("one.ts".to_string(), vec![14..18]),
3857// ("two.rs".to_string(), vec![8..12]),
3858// ("two.ts".to_string(), vec![14..18]),
3859// ]),
3860// "If no exclusions match, all files should be returned"
3861// );
3862
3863// assert_eq!(
3864// search(
3865// &project,
3866// SearchQuery::text(
3867// search_query,
3868// false,
3869// true,
3870// Vec::new(),
3871// vec![PathMatcher::new("*.rs").unwrap()],
3872// )
3873// .unwrap(),
3874// cx
3875// )
3876// .await
3877// .unwrap(),
3878// HashMap::from_iter([
3879// ("one.ts".to_string(), vec![14..18]),
3880// ("two.ts".to_string(), vec![14..18]),
3881// ]),
3882// "Rust exclusion search should give only TypeScript files"
3883// );
3884
3885// assert_eq!(
3886// search(
3887// &project,
3888// SearchQuery::text(
3889// search_query,
3890// false,
3891// true,
3892// Vec::new(),
3893// vec![
3894// PathMatcher::new("*.ts").unwrap(),
3895// PathMatcher::new("*.odd").unwrap(),
3896// ],
3897// ).unwrap(),
3898// cx
3899// )
3900// .await
3901// .unwrap(),
3902// HashMap::from_iter([
3903// ("one.rs".to_string(), vec![8..12]),
3904// ("two.rs".to_string(), vec![8..12]),
3905// ]),
3906// "TypeScript exclusion search should give only Rust files, even if other exclusions don't match anything"
3907// );
3908
3909// assert!(
3910// search(
3911// &project,
3912// SearchQuery::text(
3913// search_query,
3914// false,
3915// true,
3916// Vec::new(),
3917// vec![
3918// PathMatcher::new("*.rs").unwrap(),
3919// PathMatcher::new("*.ts").unwrap(),
3920// PathMatcher::new("*.odd").unwrap(),
3921// ],
3922// ).unwrap(),
3923// cx
3924// )
3925// .await
3926// .unwrap().is_empty(),
3927// "Rust and typescript exclusion should give no files, even if other exclusions don't match anything"
3928// );
3929// }
3930
3931// #[gpui::test]
3932// async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) {
3933// init_test(cx);
3934
3935// let search_query = "file";
3936
3937// let fs = FakeFs::new(cx.background());
3938// fs.insert_tree(
3939// "/dir",
3940// json!({
3941// "one.rs": r#"// Rust file one"#,
3942// "one.ts": r#"// TypeScript file one"#,
3943// "two.rs": r#"// Rust file two"#,
3944// "two.ts": r#"// TypeScript file two"#,
3945// }),
3946// )
3947// .await;
3948// let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3949
3950// assert!(
3951// search(
3952// &project,
3953// SearchQuery::text(
3954// search_query,
3955// false,
3956// true,
3957// vec![PathMatcher::new("*.odd").unwrap()],
3958// vec![PathMatcher::new("*.odd").unwrap()],
3959// )
3960// .unwrap(),
3961// cx
3962// )
3963// .await
3964// .unwrap()
3965// .is_empty(),
3966// "If both no exclusions and inclusions match, exclusions should win and return nothing"
3967// );
3968
3969// assert!(
3970// search(
3971// &project,
3972// SearchQuery::text(
3973// search_query,
3974// false,
3975// true,
3976// vec![PathMatcher::new("*.ts").unwrap()],
3977// vec![PathMatcher::new("*.ts").unwrap()],
3978// ).unwrap(),
3979// cx
3980// )
3981// .await
3982// .unwrap()
3983// .is_empty(),
3984// "If both TypeScript exclusions and inclusions match, exclusions should win and return nothing files."
3985// );
3986
3987// assert!(
3988// search(
3989// &project,
3990// SearchQuery::text(
3991// search_query,
3992// false,
3993// true,
3994// vec![
3995// PathMatcher::new("*.ts").unwrap(),
3996// PathMatcher::new("*.odd").unwrap()
3997// ],
3998// vec![
3999// PathMatcher::new("*.ts").unwrap(),
4000// PathMatcher::new("*.odd").unwrap()
4001// ],
4002// )
4003// .unwrap(),
4004// cx
4005// )
4006// .await
4007// .unwrap()
4008// .is_empty(),
4009// "Non-matching inclusions and exclusions should not change that."
4010// );
4011
4012// assert_eq!(
4013// search(
4014// &project,
4015// SearchQuery::text(
4016// search_query,
4017// false,
4018// true,
4019// vec![
4020// PathMatcher::new("*.ts").unwrap(),
4021// PathMatcher::new("*.odd").unwrap()
4022// ],
4023// vec![
4024// PathMatcher::new("*.rs").unwrap(),
4025// PathMatcher::new("*.odd").unwrap()
4026// ],
4027// )
4028// .unwrap(),
4029// cx
4030// )
4031// .await
4032// .unwrap(),
4033// HashMap::from_iter([
4034// ("one.ts".to_string(), vec![14..18]),
4035// ("two.ts".to_string(), vec![14..18]),
4036// ]),
4037// "Non-intersecting TypeScript inclusions and Rust exclusions should return TypeScript files"
4038// );
4039// }
4040
4041// #[test]
4042// fn test_glob_literal_prefix() {
4043// assert_eq!(glob_literal_prefix("**/*.js"), "");
4044// assert_eq!(glob_literal_prefix("node_modules/**/*.js"), "node_modules");
4045// assert_eq!(glob_literal_prefix("foo/{bar,baz}.js"), "foo");
4046// assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js");
4047// }
4048
4049// async fn search(
4050// project: &ModelHandle<Project>,
4051// query: SearchQuery,
4052// cx: &mut gpui::TestAppContext,
4053// ) -> Result<HashMap<String, Vec<Range<usize>>>> {
4054// let mut search_rx = project.update(cx, |project, cx| project.search(query, cx));
4055// let mut result = HashMap::default();
4056// while let Some((buffer, range)) = search_rx.next().await {
4057// result.entry(buffer).or_insert(range);
4058// }
4059// Ok(result
4060// .into_iter()
4061// .map(|(buffer, ranges)| {
4062// buffer.read_with(cx, |buffer, _| {
4063// let path = buffer.file().unwrap().path().to_string_lossy().to_string();
4064// let ranges = ranges
4065// .into_iter()
4066// .map(|range| range.to_offset(buffer))
4067// .collect::<Vec<_>>();
4068// (path, ranges)
4069// })
4070// })
4071// .collect())
4072// }
4073
4074// fn init_test(cx: &mut gpui::TestAppContext) {
4075// cx.foreground().forbid_parking();
4076
4077// cx.update(|cx| {
4078// cx.set_global(SettingsStore::test(cx));
4079// language2::init(cx);
4080// Project::init_settings(cx);
4081// });
4082// }