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