1use super::*;
2use clock::ReplicaId;
3use gpui::{ModelHandle, MutableAppContext};
4use rand::prelude::*;
5use std::{
6 cell::RefCell,
7 env,
8 iter::FromIterator,
9 ops::Range,
10 rc::Rc,
11 time::{Duration, Instant},
12};
13use unindent::Unindent as _;
14use util::test::Network;
15
16#[cfg(test)]
17#[ctor::ctor]
18fn init_logger() {
19 // std::env::set_var("RUST_LOG", "info");
20 env_logger::init();
21}
22
23#[test]
24fn test_select_language() {
25 let registry = LanguageRegistry {
26 languages: vec![
27 Arc::new(Language::new(
28 LanguageConfig {
29 name: "Rust".to_string(),
30 path_suffixes: vec!["rs".to_string()],
31 ..Default::default()
32 },
33 Some(tree_sitter_rust::language()),
34 )),
35 Arc::new(Language::new(
36 LanguageConfig {
37 name: "Make".to_string(),
38 path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
39 ..Default::default()
40 },
41 Some(tree_sitter_rust::language()),
42 )),
43 ],
44 };
45
46 // matching file extension
47 assert_eq!(
48 registry.select_language("zed/lib.rs").map(|l| l.name()),
49 Some("Rust")
50 );
51 assert_eq!(
52 registry.select_language("zed/lib.mk").map(|l| l.name()),
53 Some("Make")
54 );
55
56 // matching filename
57 assert_eq!(
58 registry.select_language("zed/Makefile").map(|l| l.name()),
59 Some("Make")
60 );
61
62 // matching suffix that is not the full file extension or filename
63 assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
64 assert_eq!(
65 registry.select_language("zed/a.cars").map(|l| l.name()),
66 None
67 );
68 assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
69}
70
71#[gpui::test]
72fn test_edit_events(cx: &mut gpui::MutableAppContext) {
73 let mut now = Instant::now();
74 let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
75 let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
76
77 let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx));
78 let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx));
79 let buffer_ops = buffer1.update(cx, |buffer, cx| {
80 let buffer_1_events = buffer_1_events.clone();
81 cx.subscribe(&buffer1, move |_, _, event, _| {
82 buffer_1_events.borrow_mut().push(event.clone())
83 })
84 .detach();
85 let buffer_2_events = buffer_2_events.clone();
86 cx.subscribe(&buffer2, move |_, _, event, _| {
87 buffer_2_events.borrow_mut().push(event.clone())
88 })
89 .detach();
90
91 // An edit emits an edited event, followed by a dirtied event,
92 // since the buffer was previously in a clean state.
93 buffer.edit(Some(2..4), "XYZ", cx);
94
95 // An empty transaction does not emit any events.
96 buffer.start_transaction();
97 buffer.end_transaction(cx);
98
99 // A transaction containing two edits emits one edited event.
100 now += Duration::from_secs(1);
101 buffer.start_transaction_at(now);
102 buffer.edit(Some(5..5), "u", cx);
103 buffer.edit(Some(6..6), "w", cx);
104 buffer.end_transaction_at(now, cx);
105
106 // Undoing a transaction emits one edited event.
107 buffer.undo(cx);
108
109 buffer.operations.clone()
110 });
111
112 // Incorporating a set of remote ops emits a single edited event,
113 // followed by a dirtied event.
114 buffer2.update(cx, |buffer, cx| {
115 buffer.apply_ops(buffer_ops, cx).unwrap();
116 });
117
118 let buffer_1_events = buffer_1_events.borrow();
119 assert_eq!(
120 *buffer_1_events,
121 vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited]
122 );
123
124 let buffer_2_events = buffer_2_events.borrow();
125 assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
126}
127
128#[gpui::test]
129async fn test_apply_diff(mut cx: gpui::TestAppContext) {
130 let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
131 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
132
133 let text = "a\nccc\ndddd\nffffff\n";
134 let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
135 buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
136 cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
137
138 let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
139 let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
140 buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
141 cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
142}
143
144#[gpui::test]
145async fn test_reparse(mut cx: gpui::TestAppContext) {
146 let text = "fn a() {}";
147 let buffer = cx.add_model(|cx| {
148 Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx)
149 });
150
151 // Wait for the initial text to parse
152 buffer
153 .condition(&cx, |buffer, _| !buffer.is_parsing())
154 .await;
155 assert_eq!(
156 get_tree_sexp(&buffer, &cx),
157 concat!(
158 "(source_file (function_item name: (identifier) ",
159 "parameters: (parameters) ",
160 "body: (block)))"
161 )
162 );
163
164 buffer.update(&mut cx, |buffer, _| {
165 buffer.set_sync_parse_timeout(Duration::ZERO)
166 });
167
168 // Perform some edits (add parameter and variable reference)
169 // Parsing doesn't begin until the transaction is complete
170 buffer.update(&mut cx, |buf, cx| {
171 buf.start_transaction();
172
173 let offset = buf.text().find(")").unwrap();
174 buf.edit(vec![offset..offset], "b: C", cx);
175 assert!(!buf.is_parsing());
176
177 let offset = buf.text().find("}").unwrap();
178 buf.edit(vec![offset..offset], " d; ", cx);
179 assert!(!buf.is_parsing());
180
181 buf.end_transaction(cx);
182 assert_eq!(buf.text(), "fn a(b: C) { d; }");
183 assert!(buf.is_parsing());
184 });
185 buffer
186 .condition(&cx, |buffer, _| !buffer.is_parsing())
187 .await;
188 assert_eq!(
189 get_tree_sexp(&buffer, &cx),
190 concat!(
191 "(source_file (function_item name: (identifier) ",
192 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
193 "body: (block (identifier))))"
194 )
195 );
196
197 // Perform a series of edits without waiting for the current parse to complete:
198 // * turn identifier into a field expression
199 // * turn field expression into a method call
200 // * add a turbofish to the method call
201 buffer.update(&mut cx, |buf, cx| {
202 let offset = buf.text().find(";").unwrap();
203 buf.edit(vec![offset..offset], ".e", cx);
204 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
205 assert!(buf.is_parsing());
206 });
207 buffer.update(&mut cx, |buf, cx| {
208 let offset = buf.text().find(";").unwrap();
209 buf.edit(vec![offset..offset], "(f)", cx);
210 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
211 assert!(buf.is_parsing());
212 });
213 buffer.update(&mut cx, |buf, cx| {
214 let offset = buf.text().find("(f)").unwrap();
215 buf.edit(vec![offset..offset], "::<G>", cx);
216 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
217 assert!(buf.is_parsing());
218 });
219 buffer
220 .condition(&cx, |buffer, _| !buffer.is_parsing())
221 .await;
222 assert_eq!(
223 get_tree_sexp(&buffer, &cx),
224 concat!(
225 "(source_file (function_item name: (identifier) ",
226 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
227 "body: (block (call_expression ",
228 "function: (generic_function ",
229 "function: (field_expression value: (identifier) field: (field_identifier)) ",
230 "type_arguments: (type_arguments (type_identifier))) ",
231 "arguments: (arguments (identifier))))))",
232 )
233 );
234
235 buffer.update(&mut cx, |buf, cx| {
236 buf.undo(cx);
237 assert_eq!(buf.text(), "fn a() {}");
238 assert!(buf.is_parsing());
239 });
240 buffer
241 .condition(&cx, |buffer, _| !buffer.is_parsing())
242 .await;
243 assert_eq!(
244 get_tree_sexp(&buffer, &cx),
245 concat!(
246 "(source_file (function_item name: (identifier) ",
247 "parameters: (parameters) ",
248 "body: (block)))"
249 )
250 );
251
252 buffer.update(&mut cx, |buf, cx| {
253 buf.redo(cx);
254 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
255 assert!(buf.is_parsing());
256 });
257 buffer
258 .condition(&cx, |buffer, _| !buffer.is_parsing())
259 .await;
260 assert_eq!(
261 get_tree_sexp(&buffer, &cx),
262 concat!(
263 "(source_file (function_item name: (identifier) ",
264 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
265 "body: (block (call_expression ",
266 "function: (generic_function ",
267 "function: (field_expression value: (identifier) field: (field_identifier)) ",
268 "type_arguments: (type_arguments (type_identifier))) ",
269 "arguments: (arguments (identifier))))))",
270 )
271 );
272
273 fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
274 buffer.read_with(cx, |buffer, _| {
275 buffer.syntax_tree().unwrap().root_node().to_sexp()
276 })
277 }
278}
279
280#[gpui::test]
281fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
282 let buffer = cx.add_model(|cx| {
283 let text = "
284 mod x {
285 mod y {
286
287 }
288 }
289 "
290 .unindent();
291 Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx)
292 });
293 let buffer = buffer.read(cx);
294 assert_eq!(
295 buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)),
296 Some((
297 Point::new(0, 6)..Point::new(0, 7),
298 Point::new(4, 0)..Point::new(4, 1)
299 ))
300 );
301 assert_eq!(
302 buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)),
303 Some((
304 Point::new(1, 10)..Point::new(1, 11),
305 Point::new(3, 4)..Point::new(3, 5)
306 ))
307 );
308 assert_eq!(
309 buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)),
310 Some((
311 Point::new(1, 10)..Point::new(1, 11),
312 Point::new(3, 4)..Point::new(3, 5)
313 ))
314 );
315}
316
317#[gpui::test]
318fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
319 cx.add_model(|cx| {
320 let text = "fn a() {}";
321 let mut buffer =
322 Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
323
324 buffer.edit_with_autoindent([8..8], "\n\n", cx);
325 assert_eq!(buffer.text(), "fn a() {\n \n}");
326
327 buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", cx);
328 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
329
330 buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", cx);
331 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
332
333 buffer
334 });
335}
336
337#[gpui::test]
338fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
339 cx.add_model(|cx| {
340 let text = "
341 fn a() {
342 c;
343 d;
344 }
345 "
346 .unindent();
347
348 let mut buffer =
349 Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
350
351 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
352 // their indentation is not adjusted.
353 buffer.edit_with_autoindent([empty(Point::new(1, 1)), empty(Point::new(2, 1))], "()", cx);
354 assert_eq!(
355 buffer.text(),
356 "
357 fn a() {
358 c();
359 d();
360 }
361 "
362 .unindent()
363 );
364
365 // When appending new content after these lines, the indentation is based on the
366 // preceding lines' actual indentation.
367 buffer.edit_with_autoindent(
368 [empty(Point::new(1, 1)), empty(Point::new(2, 1))],
369 "\n.f\n.g",
370 cx,
371 );
372 assert_eq!(
373 buffer.text(),
374 "
375 fn a() {
376 c
377 .f
378 .g();
379 d
380 .f
381 .g();
382 }
383 "
384 .unindent()
385 );
386 buffer
387 });
388}
389
390#[gpui::test]
391fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) {
392 cx.add_model(|cx| {
393 let text = "
394 fn a() {}
395 "
396 .unindent();
397
398 let mut buffer =
399 Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
400
401 buffer.edit_with_autoindent([5..5], "\nb", cx);
402 assert_eq!(
403 buffer.text(),
404 "
405 fn a(
406 b) {}
407 "
408 .unindent()
409 );
410
411 // The indentation suggestion changed because `@end` node (a close paren)
412 // is now at the beginning of the line.
413 buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", cx);
414 assert_eq!(
415 buffer.text(),
416 "
417 fn a(
418 ) {}
419 "
420 .unindent()
421 );
422
423 buffer
424 });
425}
426
427#[gpui::test]
428async fn test_diagnostics(mut cx: gpui::TestAppContext) {
429 let (language_server, mut fake) = lsp::LanguageServer::fake(cx.background()).await;
430 let mut rust_lang = rust_lang();
431 rust_lang.config.language_server = Some(LanguageServerConfig {
432 disk_based_diagnostic_sources: HashSet::from_iter(["disk".to_string()]),
433 ..Default::default()
434 });
435
436 let text = "
437 fn a() { A }
438 fn b() { BB }
439 fn c() { CCC }
440 "
441 .unindent();
442
443 let buffer = cx.add_model(|cx| {
444 Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang)), Some(language_server), cx)
445 });
446
447 let open_notification = fake
448 .receive_notification::<lsp::notification::DidOpenTextDocument>()
449 .await;
450
451 // Edit the buffer, moving the content down
452 buffer.update(&mut cx, |buffer, cx| buffer.edit([0..0], "\n\n", cx));
453 let change_notification_1 = fake
454 .receive_notification::<lsp::notification::DidChangeTextDocument>()
455 .await;
456 assert!(change_notification_1.text_document.version > open_notification.text_document.version);
457
458 buffer.update(&mut cx, |buffer, cx| {
459 // Receive diagnostics for an earlier version of the buffer.
460 buffer
461 .update_diagnostics(
462 "lsp".into(),
463 Some(open_notification.text_document.version),
464 vec![
465 DiagnosticEntry {
466 range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
467 diagnostic: Diagnostic {
468 severity: DiagnosticSeverity::ERROR,
469 message: "undefined variable 'A'".to_string(),
470 is_disk_based: true,
471 group_id: 0,
472 is_primary: true,
473 ..Default::default()
474 },
475 },
476 DiagnosticEntry {
477 range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
478 diagnostic: Diagnostic {
479 severity: DiagnosticSeverity::ERROR,
480 message: "undefined variable 'BB'".to_string(),
481 is_disk_based: true,
482 group_id: 1,
483 is_primary: true,
484 ..Default::default()
485 },
486 },
487 DiagnosticEntry {
488 range: PointUtf16::new(2, 9)..PointUtf16::new(2, 12),
489 diagnostic: Diagnostic {
490 severity: DiagnosticSeverity::ERROR,
491 is_disk_based: true,
492 message: "undefined variable 'CCC'".to_string(),
493 group_id: 2,
494 is_primary: true,
495 ..Default::default()
496 },
497 },
498 ],
499 cx,
500 )
501 .unwrap();
502
503 // The diagnostics have moved down since they were created.
504 assert_eq!(
505 buffer
506 .snapshot()
507 .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0))
508 .collect::<Vec<_>>(),
509 &[
510 (
511 "lsp",
512 DiagnosticEntry {
513 range: Point::new(3, 9)..Point::new(3, 11),
514 diagnostic: Diagnostic {
515 severity: DiagnosticSeverity::ERROR,
516 message: "undefined variable 'BB'".to_string(),
517 is_disk_based: true,
518 group_id: 1,
519 is_primary: true,
520 ..Default::default()
521 },
522 }
523 ),
524 (
525 "lsp",
526 DiagnosticEntry {
527 range: Point::new(4, 9)..Point::new(4, 12),
528 diagnostic: Diagnostic {
529 severity: DiagnosticSeverity::ERROR,
530 message: "undefined variable 'CCC'".to_string(),
531 is_disk_based: true,
532 group_id: 2,
533 is_primary: true,
534 ..Default::default()
535 }
536 }
537 )
538 ]
539 );
540 assert_eq!(
541 chunks_with_diagnostics(buffer, 0..buffer.len()),
542 [
543 ("\n\nfn a() { ".to_string(), None),
544 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
545 (" }\nfn b() { ".to_string(), None),
546 ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
547 (" }\nfn c() { ".to_string(), None),
548 ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
549 (" }\n".to_string(), None),
550 ]
551 );
552 assert_eq!(
553 chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
554 [
555 ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
556 (" }\nfn c() { ".to_string(), None),
557 ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
558 ]
559 );
560
561 // Ensure overlapping diagnostics are highlighted correctly.
562 buffer
563 .update_diagnostics(
564 "lsp".into(),
565 Some(open_notification.text_document.version),
566 vec![
567 DiagnosticEntry {
568 range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
569 diagnostic: Diagnostic {
570 severity: DiagnosticSeverity::ERROR,
571 message: "undefined variable 'A'".to_string(),
572 is_disk_based: true,
573 group_id: 0,
574 is_primary: true,
575 ..Default::default()
576 },
577 },
578 DiagnosticEntry {
579 range: PointUtf16::new(0, 9)..PointUtf16::new(0, 12),
580 diagnostic: Diagnostic {
581 severity: DiagnosticSeverity::WARNING,
582 message: "unreachable statement".to_string(),
583 group_id: 1,
584 is_primary: true,
585 ..Default::default()
586 },
587 },
588 ],
589 cx,
590 )
591 .unwrap();
592 assert_eq!(
593 buffer
594 .snapshot()
595 .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0))
596 .collect::<Vec<_>>(),
597 &[
598 (
599 "lsp",
600 DiagnosticEntry {
601 range: Point::new(2, 9)..Point::new(2, 12),
602 diagnostic: Diagnostic {
603 severity: DiagnosticSeverity::WARNING,
604 message: "unreachable statement".to_string(),
605 group_id: 1,
606 is_primary: true,
607 ..Default::default()
608 }
609 }
610 ),
611 (
612 "lsp",
613 DiagnosticEntry {
614 range: Point::new(2, 9)..Point::new(2, 10),
615 diagnostic: Diagnostic {
616 severity: DiagnosticSeverity::ERROR,
617 message: "undefined variable 'A'".to_string(),
618 is_disk_based: true,
619 group_id: 0,
620 is_primary: true,
621 ..Default::default()
622 },
623 }
624 )
625 ]
626 );
627 assert_eq!(
628 chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
629 [
630 ("fn a() { ".to_string(), None),
631 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
632 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
633 ("\n".to_string(), None),
634 ]
635 );
636 assert_eq!(
637 chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
638 [
639 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
640 ("\n".to_string(), None),
641 ]
642 );
643 });
644
645 // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
646 // changes since the last save.
647 buffer.update(&mut cx, |buffer, cx| {
648 buffer.edit(Some(Point::new(2, 0)..Point::new(2, 0)), " ", cx);
649 buffer.edit(Some(Point::new(2, 8)..Point::new(2, 10)), "(x: usize)", cx);
650 });
651 let change_notification_2 = fake
652 .receive_notification::<lsp::notification::DidChangeTextDocument>()
653 .await;
654 assert!(
655 change_notification_2.text_document.version > change_notification_1.text_document.version
656 );
657
658 buffer.update(&mut cx, |buffer, cx| {
659 buffer
660 .update_diagnostics(
661 "lsp".into(),
662 Some(change_notification_2.text_document.version),
663 vec![
664 DiagnosticEntry {
665 range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
666 diagnostic: Diagnostic {
667 severity: DiagnosticSeverity::ERROR,
668 message: "undefined variable 'BB'".to_string(),
669 is_disk_based: true,
670 group_id: 1,
671 is_primary: true,
672 ..Default::default()
673 },
674 },
675 DiagnosticEntry {
676 range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
677 diagnostic: Diagnostic {
678 severity: DiagnosticSeverity::ERROR,
679 message: "undefined variable 'A'".to_string(),
680 is_disk_based: true,
681 group_id: 0,
682 is_primary: true,
683 ..Default::default()
684 },
685 },
686 ],
687 cx,
688 )
689 .unwrap();
690 assert_eq!(
691 buffer
692 .snapshot()
693 .diagnostics_in_range::<_, Point>(0..buffer.len())
694 .collect::<Vec<_>>(),
695 &[
696 (
697 "lsp",
698 DiagnosticEntry {
699 range: Point::new(2, 21)..Point::new(2, 22),
700 diagnostic: Diagnostic {
701 severity: DiagnosticSeverity::ERROR,
702 message: "undefined variable 'A'".to_string(),
703 is_disk_based: true,
704 group_id: 0,
705 is_primary: true,
706 ..Default::default()
707 }
708 }
709 ),
710 (
711 "lsp",
712 DiagnosticEntry {
713 range: Point::new(3, 9)..Point::new(3, 11),
714 diagnostic: Diagnostic {
715 severity: DiagnosticSeverity::ERROR,
716 message: "undefined variable 'BB'".to_string(),
717 is_disk_based: true,
718 group_id: 1,
719 is_primary: true,
720 ..Default::default()
721 },
722 }
723 )
724 ]
725 );
726 });
727}
728
729#[gpui::test]
730async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
731 cx.add_model(|cx| {
732 let text = concat!(
733 "let one = ;\n", //
734 "let two = \n",
735 "let three = 3;\n",
736 );
737
738 let mut buffer = Buffer::new(0, text, cx);
739 buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
740 buffer
741 .update_diagnostics(
742 "lsp".into(),
743 None,
744 vec![
745 DiagnosticEntry {
746 range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10),
747 diagnostic: Diagnostic {
748 severity: DiagnosticSeverity::ERROR,
749 message: "syntax error 1".to_string(),
750 ..Default::default()
751 },
752 },
753 DiagnosticEntry {
754 range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10),
755 diagnostic: Diagnostic {
756 severity: DiagnosticSeverity::ERROR,
757 message: "syntax error 2".to_string(),
758 ..Default::default()
759 },
760 },
761 ],
762 cx,
763 )
764 .unwrap();
765
766 // An empty range is extended forward to include the following character.
767 // At the end of a line, an empty range is extended backward to include
768 // the preceding character.
769 let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len());
770 assert_eq!(
771 chunks
772 .iter()
773 .map(|(s, d)| (s.as_str(), *d))
774 .collect::<Vec<_>>(),
775 &[
776 ("let one = ", None),
777 (";", Some(DiagnosticSeverity::ERROR)),
778 ("\nlet two =", None),
779 (" ", Some(DiagnosticSeverity::ERROR)),
780 ("\nlet three = 3;\n", None)
781 ]
782 );
783 buffer
784 });
785}
786
787#[gpui::test]
788fn test_serialization(cx: &mut gpui::MutableAppContext) {
789 let mut now = Instant::now();
790
791 let buffer1 = cx.add_model(|cx| {
792 let mut buffer = Buffer::new(0, "abc", cx);
793 buffer.edit([3..3], "D", cx);
794
795 now += Duration::from_secs(1);
796 buffer.start_transaction_at(now);
797 buffer.edit([4..4], "E", cx);
798 buffer.end_transaction_at(now, cx);
799 assert_eq!(buffer.text(), "abcDE");
800
801 buffer.undo(cx);
802 assert_eq!(buffer.text(), "abcD");
803
804 buffer.edit([4..4], "F", cx);
805 assert_eq!(buffer.text(), "abcDF");
806 buffer
807 });
808 assert_eq!(buffer1.read(cx).text(), "abcDF");
809
810 let message = buffer1.read(cx).to_proto();
811 let buffer2 = cx.add_model(|cx| Buffer::from_proto(1, message, None, cx).unwrap());
812 assert_eq!(buffer2.read(cx).text(), "abcDF");
813}
814
815#[gpui::test(iterations = 100)]
816fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
817 let min_peers = env::var("MIN_PEERS")
818 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
819 .unwrap_or(1);
820 let max_peers = env::var("MAX_PEERS")
821 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
822 .unwrap_or(5);
823 let operations = env::var("OPERATIONS")
824 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
825 .unwrap_or(10);
826
827 let base_text_len = rng.gen_range(0..10);
828 let base_text = RandomCharIter::new(&mut rng)
829 .take(base_text_len)
830 .collect::<String>();
831 let mut replica_ids = Vec::new();
832 let mut buffers = Vec::new();
833 let mut network = Network::new(rng.clone());
834
835 for i in 0..rng.gen_range(min_peers..=max_peers) {
836 let buffer = cx.add_model(|cx| {
837 let mut buffer = Buffer::new(i as ReplicaId, base_text.as_str(), cx);
838 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
839 buffer
840 });
841 buffers.push(buffer);
842 replica_ids.push(i as ReplicaId);
843 network.add_peer(i as ReplicaId);
844 log::info!("Adding initial peer with replica id {}", i);
845 }
846
847 log::info!("initial text: {:?}", base_text);
848
849 let mut now = Instant::now();
850 let mut mutation_count = operations;
851 loop {
852 let replica_index = rng.gen_range(0..replica_ids.len());
853 let replica_id = replica_ids[replica_index];
854 let buffer = &mut buffers[replica_index];
855 let mut new_buffer = None;
856 match rng.gen_range(0..100) {
857 0..=34 if mutation_count != 0 => {
858 buffer.update(cx, |buffer, cx| {
859 buffer.start_transaction_at(now);
860 buffer.randomly_edit(&mut rng, 5, cx);
861 buffer.end_transaction_at(now, cx);
862 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
863 });
864 mutation_count -= 1;
865 }
866 35..=49 if replica_ids.len() < max_peers => {
867 let old_buffer = buffer.read(cx).to_proto();
868 let new_replica_id = replica_ids.len() as ReplicaId;
869 log::info!(
870 "Adding new replica {} (replicating from {})",
871 new_replica_id,
872 replica_id
873 );
874 new_buffer = Some(cx.add_model(|cx| {
875 let mut new_buffer =
876 Buffer::from_proto(new_replica_id, old_buffer, None, cx).unwrap();
877 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
878 new_buffer
879 }));
880 replica_ids.push(new_replica_id);
881 network.replicate(replica_id, new_replica_id);
882 }
883 50..=69 if mutation_count != 0 => {
884 buffer.update(cx, |buffer, cx| {
885 buffer.randomly_undo_redo(&mut rng, cx);
886 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
887 });
888 mutation_count -= 1;
889 }
890 70..=99 if network.has_unreceived(replica_id) => {
891 let ops = network
892 .receive(replica_id)
893 .into_iter()
894 .map(|op| proto::deserialize_operation(op).unwrap());
895 if ops.len() > 0 {
896 log::info!(
897 "peer {} applying {} ops from the network.",
898 replica_id,
899 ops.len()
900 );
901 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
902 }
903 }
904 _ => {}
905 }
906
907 buffer.update(cx, |buffer, _| {
908 let ops = buffer
909 .operations
910 .drain(..)
911 .map(|op| proto::serialize_operation(&op))
912 .collect();
913 network.broadcast(buffer.replica_id(), ops);
914 });
915 now += Duration::from_millis(rng.gen_range(0..=200));
916 buffers.extend(new_buffer);
917
918 if mutation_count == 0 && network.is_idle() {
919 break;
920 }
921 }
922
923 let first_buffer = buffers[0].read(cx);
924 for buffer in &buffers[1..] {
925 let buffer = buffer.read(cx);
926 assert_eq!(
927 buffer.text(),
928 first_buffer.text(),
929 "Replica {} text != Replica 0 text",
930 buffer.replica_id()
931 );
932 }
933}
934
935fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
936 buffer: &Buffer,
937 range: Range<T>,
938) -> Vec<(String, Option<DiagnosticSeverity>)> {
939 let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
940 for chunk in buffer.snapshot().chunks(range, Some(&Default::default())) {
941 if chunks
942 .last()
943 .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
944 {
945 chunks.last_mut().unwrap().0.push_str(chunk.text);
946 } else {
947 chunks.push((chunk.text.to_string(), chunk.diagnostic));
948 }
949 }
950 chunks
951}
952
953#[test]
954fn test_contiguous_ranges() {
955 assert_eq!(
956 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
957 &[1..4, 5..7, 9..13]
958 );
959
960 // Respects the `max_len` parameter
961 assert_eq!(
962 contiguous_ranges(
963 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
964 3
965 )
966 .collect::<Vec<_>>(),
967 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
968 );
969}
970
971impl Buffer {
972 pub fn enclosing_bracket_point_ranges<T: ToOffset>(
973 &self,
974 range: Range<T>,
975 ) -> Option<(Range<Point>, Range<Point>)> {
976 self.snapshot()
977 .enclosing_bracket_ranges(range)
978 .map(|(start, end)| {
979 let point_start = start.start.to_point(self)..start.end.to_point(self);
980 let point_end = end.start.to_point(self)..end.end.to_point(self);
981 (point_start, point_end)
982 })
983 }
984}
985
986fn rust_lang() -> Language {
987 Language::new(
988 LanguageConfig {
989 name: "Rust".to_string(),
990 path_suffixes: vec!["rs".to_string()],
991 language_server: None,
992 ..Default::default()
993 },
994 Some(tree_sitter_rust::language()),
995 )
996 .with_indents_query(
997 r#"
998 (call_expression) @indent
999 (field_expression) @indent
1000 (_ "(" ")" @end) @indent
1001 (_ "{" "}" @end) @indent
1002 "#,
1003 )
1004 .unwrap()
1005 .with_brackets_query(r#" ("{" @open "}" @close) "#)
1006 .unwrap()
1007}
1008
1009fn empty(point: Point) -> Range<Point> {
1010 point..point
1011}