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 "lsp".into(),
464 Some(open_notification.text_document.version),
465 vec![
466 DiagnosticEntry {
467 range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
468 diagnostic: Diagnostic {
469 severity: DiagnosticSeverity::ERROR,
470 message: "undefined variable 'A'".to_string(),
471 is_disk_based: true,
472 group_id: 0,
473 is_primary: true,
474 ..Default::default()
475 },
476 },
477 DiagnosticEntry {
478 range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
479 diagnostic: Diagnostic {
480 severity: DiagnosticSeverity::ERROR,
481 message: "undefined variable 'BB'".to_string(),
482 is_disk_based: true,
483 group_id: 1,
484 is_primary: true,
485 ..Default::default()
486 },
487 },
488 DiagnosticEntry {
489 range: PointUtf16::new(2, 9)..PointUtf16::new(2, 12),
490 diagnostic: Diagnostic {
491 severity: DiagnosticSeverity::ERROR,
492 is_disk_based: true,
493 message: "undefined variable 'CCC'".to_string(),
494 group_id: 2,
495 is_primary: true,
496 ..Default::default()
497 },
498 },
499 ],
500 cx,
501 )
502 .unwrap();
503
504 // The diagnostics have moved down since they were created.
505 assert_eq!(
506 buffer
507 .snapshot()
508 .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0))
509 .collect::<Vec<_>>(),
510 &[
511 (
512 "lsp",
513 DiagnosticEntry {
514 range: Point::new(3, 9)..Point::new(3, 11),
515 diagnostic: Diagnostic {
516 severity: DiagnosticSeverity::ERROR,
517 message: "undefined variable 'BB'".to_string(),
518 is_disk_based: true,
519 group_id: 1,
520 is_primary: true,
521 ..Default::default()
522 },
523 }
524 ),
525 (
526 "lsp",
527 DiagnosticEntry {
528 range: Point::new(4, 9)..Point::new(4, 12),
529 diagnostic: Diagnostic {
530 severity: DiagnosticSeverity::ERROR,
531 message: "undefined variable 'CCC'".to_string(),
532 is_disk_based: true,
533 group_id: 2,
534 is_primary: true,
535 ..Default::default()
536 }
537 }
538 )
539 ]
540 );
541 assert_eq!(
542 chunks_with_diagnostics(buffer, 0..buffer.len()),
543 [
544 ("\n\nfn a() { ".to_string(), None),
545 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
546 (" }\nfn b() { ".to_string(), None),
547 ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
548 (" }\nfn c() { ".to_string(), None),
549 ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
550 (" }\n".to_string(), None),
551 ]
552 );
553 assert_eq!(
554 chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
555 [
556 ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
557 (" }\nfn c() { ".to_string(), None),
558 ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
559 ]
560 );
561
562 // Ensure overlapping diagnostics are highlighted correctly.
563 buffer
564 .update_diagnostics(
565 "lsp".into(),
566 Some(open_notification.text_document.version),
567 vec![
568 DiagnosticEntry {
569 range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
570 diagnostic: Diagnostic {
571 severity: DiagnosticSeverity::ERROR,
572 message: "undefined variable 'A'".to_string(),
573 is_disk_based: true,
574 group_id: 0,
575 is_primary: true,
576 ..Default::default()
577 },
578 },
579 DiagnosticEntry {
580 range: PointUtf16::new(0, 9)..PointUtf16::new(0, 12),
581 diagnostic: Diagnostic {
582 severity: DiagnosticSeverity::WARNING,
583 message: "unreachable statement".to_string(),
584 group_id: 1,
585 is_primary: true,
586 ..Default::default()
587 },
588 },
589 ],
590 cx,
591 )
592 .unwrap();
593 assert_eq!(
594 buffer
595 .snapshot()
596 .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0))
597 .collect::<Vec<_>>(),
598 &[
599 (
600 "lsp",
601 DiagnosticEntry {
602 range: Point::new(2, 9)..Point::new(2, 12),
603 diagnostic: Diagnostic {
604 severity: DiagnosticSeverity::WARNING,
605 message: "unreachable statement".to_string(),
606 group_id: 1,
607 is_primary: true,
608 ..Default::default()
609 }
610 }
611 ),
612 (
613 "lsp",
614 DiagnosticEntry {
615 range: Point::new(2, 9)..Point::new(2, 10),
616 diagnostic: Diagnostic {
617 severity: DiagnosticSeverity::ERROR,
618 message: "undefined variable 'A'".to_string(),
619 is_disk_based: true,
620 group_id: 0,
621 is_primary: true,
622 ..Default::default()
623 },
624 }
625 )
626 ]
627 );
628 assert_eq!(
629 chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
630 [
631 ("fn a() { ".to_string(), None),
632 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
633 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
634 ("\n".to_string(), None),
635 ]
636 );
637 assert_eq!(
638 chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
639 [
640 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
641 ("\n".to_string(), None),
642 ]
643 );
644 });
645
646 // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
647 // changes since the last save.
648 buffer.update(&mut cx, |buffer, cx| {
649 buffer.edit(Some(Point::new(2, 0)..Point::new(2, 0)), " ", cx);
650 buffer.edit(Some(Point::new(2, 8)..Point::new(2, 10)), "(x: usize)", cx);
651 });
652 let change_notification_2 = fake
653 .receive_notification::<lsp::notification::DidChangeTextDocument>()
654 .await;
655 assert!(
656 change_notification_2.text_document.version > change_notification_1.text_document.version
657 );
658
659 buffer.update(&mut cx, |buffer, cx| {
660 buffer
661 .update_diagnostics(
662 "lsp".into(),
663 Some(change_notification_2.text_document.version),
664 vec![
665 DiagnosticEntry {
666 range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
667 diagnostic: Diagnostic {
668 severity: DiagnosticSeverity::ERROR,
669 message: "undefined variable 'BB'".to_string(),
670 is_disk_based: true,
671 group_id: 1,
672 is_primary: true,
673 ..Default::default()
674 },
675 },
676 DiagnosticEntry {
677 range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
678 diagnostic: Diagnostic {
679 severity: DiagnosticSeverity::ERROR,
680 message: "undefined variable 'A'".to_string(),
681 is_disk_based: true,
682 group_id: 0,
683 is_primary: true,
684 ..Default::default()
685 },
686 },
687 ],
688 cx,
689 )
690 .unwrap();
691 assert_eq!(
692 buffer
693 .snapshot()
694 .diagnostics_in_range::<_, Point>(0..buffer.len())
695 .collect::<Vec<_>>(),
696 &[
697 (
698 "lsp",
699 DiagnosticEntry {
700 range: Point::new(2, 21)..Point::new(2, 22),
701 diagnostic: Diagnostic {
702 severity: DiagnosticSeverity::ERROR,
703 message: "undefined variable 'A'".to_string(),
704 is_disk_based: true,
705 group_id: 0,
706 is_primary: true,
707 ..Default::default()
708 }
709 }
710 ),
711 (
712 "lsp",
713 DiagnosticEntry {
714 range: Point::new(3, 9)..Point::new(3, 11),
715 diagnostic: Diagnostic {
716 severity: DiagnosticSeverity::ERROR,
717 message: "undefined variable 'BB'".to_string(),
718 is_disk_based: true,
719 group_id: 1,
720 is_primary: true,
721 ..Default::default()
722 },
723 }
724 )
725 ]
726 );
727 });
728}
729
730#[gpui::test]
731async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
732 cx.add_model(|cx| {
733 let text = concat!(
734 "let one = ;\n", //
735 "let two = \n",
736 "let three = 3;\n",
737 );
738
739 let mut buffer = Buffer::new(0, text, cx);
740 buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
741 buffer
742 .update_diagnostics(
743 "lsp".into(),
744 None,
745 vec![
746 DiagnosticEntry {
747 range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10),
748 diagnostic: Diagnostic {
749 severity: DiagnosticSeverity::ERROR,
750 message: "syntax error 1".to_string(),
751 ..Default::default()
752 },
753 },
754 DiagnosticEntry {
755 range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10),
756 diagnostic: Diagnostic {
757 severity: DiagnosticSeverity::ERROR,
758 message: "syntax error 2".to_string(),
759 ..Default::default()
760 },
761 },
762 ],
763 cx,
764 )
765 .unwrap();
766
767 // An empty range is extended forward to include the following character.
768 // At the end of a line, an empty range is extended backward to include
769 // the preceding character.
770 let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len());
771 assert_eq!(
772 chunks
773 .iter()
774 .map(|(s, d)| (s.as_str(), *d))
775 .collect::<Vec<_>>(),
776 &[
777 ("let one = ", None),
778 (";", Some(DiagnosticSeverity::ERROR)),
779 ("\nlet two =", None),
780 (" ", Some(DiagnosticSeverity::ERROR)),
781 ("\nlet three = 3;\n", None)
782 ]
783 );
784 buffer
785 });
786}
787
788#[gpui::test]
789fn test_serialization(cx: &mut gpui::MutableAppContext) {
790 let mut now = Instant::now();
791
792 let buffer1 = cx.add_model(|cx| {
793 let mut buffer = Buffer::new(0, "abc", cx);
794 buffer.edit([3..3], "D", cx);
795
796 now += Duration::from_secs(1);
797 buffer.start_transaction_at(now);
798 buffer.edit([4..4], "E", cx);
799 buffer.end_transaction_at(now, cx);
800 assert_eq!(buffer.text(), "abcDE");
801
802 buffer.undo(cx);
803 assert_eq!(buffer.text(), "abcD");
804
805 buffer.edit([4..4], "F", cx);
806 assert_eq!(buffer.text(), "abcDF");
807 buffer
808 });
809 assert_eq!(buffer1.read(cx).text(), "abcDF");
810
811 let message = buffer1.read(cx).to_proto();
812 let buffer2 = cx.add_model(|cx| Buffer::from_proto(1, message, None, cx).unwrap());
813 assert_eq!(buffer2.read(cx).text(), "abcDF");
814}
815
816#[gpui::test(iterations = 100)]
817fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
818 let min_peers = env::var("MIN_PEERS")
819 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
820 .unwrap_or(1);
821 let max_peers = env::var("MAX_PEERS")
822 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
823 .unwrap_or(5);
824 let operations = env::var("OPERATIONS")
825 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
826 .unwrap_or(10);
827
828 let base_text_len = rng.gen_range(0..10);
829 let base_text = RandomCharIter::new(&mut rng)
830 .take(base_text_len)
831 .collect::<String>();
832 let mut replica_ids = Vec::new();
833 let mut buffers = Vec::new();
834 let mut network = Network::new(rng.clone());
835
836 for i in 0..rng.gen_range(min_peers..=max_peers) {
837 let buffer = cx.add_model(|cx| {
838 let mut buffer = Buffer::new(i as ReplicaId, base_text.as_str(), cx);
839 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
840 buffer
841 });
842 buffers.push(buffer);
843 replica_ids.push(i as ReplicaId);
844 network.add_peer(i as ReplicaId);
845 log::info!("Adding initial peer with replica id {}", i);
846 }
847
848 log::info!("initial text: {:?}", base_text);
849
850 let mut now = Instant::now();
851 let mut mutation_count = operations;
852 let mut active_selections = BTreeMap::default();
853 loop {
854 let replica_index = rng.gen_range(0..replica_ids.len());
855 let replica_id = replica_ids[replica_index];
856 let buffer = &mut buffers[replica_index];
857 let mut new_buffer = None;
858 match rng.gen_range(0..100) {
859 0..=29 if mutation_count != 0 => {
860 buffer.update(cx, |buffer, cx| {
861 buffer.start_transaction_at(now);
862 buffer.randomly_edit(&mut rng, 5, cx);
863 buffer.end_transaction_at(now, cx);
864 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
865 });
866 mutation_count -= 1;
867 }
868 30..=39 if mutation_count != 0 => {
869 buffer.update(cx, |buffer, cx| {
870 let mut selections = Vec::new();
871 for id in 0..rng.gen_range(1..=5) {
872 let range = buffer.random_byte_range(0, &mut rng);
873 selections.push(Selection {
874 id,
875 start: buffer.anchor_before(range.start),
876 end: buffer.anchor_before(range.end),
877 reversed: false,
878 goal: SelectionGoal::None,
879 });
880 }
881 let selections: Arc<[Selection<Anchor>]> = selections.into();
882 log::info!(
883 "peer {} setting active selections: {:?}",
884 replica_id,
885 selections
886 );
887 active_selections.insert(replica_id, selections.clone());
888 buffer.set_active_selections(selections, cx);
889 });
890 mutation_count -= 1;
891 }
892 40..=49 if replica_ids.len() < max_peers => {
893 let old_buffer = buffer.read(cx).to_proto();
894 let new_replica_id = replica_ids.len() as ReplicaId;
895 log::info!(
896 "Adding new replica {} (replicating from {})",
897 new_replica_id,
898 replica_id
899 );
900 new_buffer = Some(cx.add_model(|cx| {
901 let mut new_buffer =
902 Buffer::from_proto(new_replica_id, old_buffer, None, cx).unwrap();
903 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
904 new_buffer
905 }));
906 replica_ids.push(new_replica_id);
907 network.replicate(replica_id, new_replica_id);
908 }
909 50..=69 if mutation_count != 0 => {
910 buffer.update(cx, |buffer, cx| {
911 buffer.randomly_undo_redo(&mut rng, cx);
912 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
913 });
914 mutation_count -= 1;
915 }
916 70..=99 if network.has_unreceived(replica_id) => {
917 let ops = network
918 .receive(replica_id)
919 .into_iter()
920 .map(|op| proto::deserialize_operation(op).unwrap());
921 if ops.len() > 0 {
922 log::info!(
923 "peer {} applying {} ops from the network.",
924 replica_id,
925 ops.len()
926 );
927 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
928 }
929 }
930 _ => {}
931 }
932
933 buffer.update(cx, |buffer, _| {
934 let ops = buffer
935 .operations
936 .drain(..)
937 .map(|op| proto::serialize_operation(&op))
938 .collect();
939 network.broadcast(buffer.replica_id(), ops);
940 });
941 now += Duration::from_millis(rng.gen_range(0..=200));
942 buffers.extend(new_buffer);
943
944 if mutation_count == 0 && network.is_idle() {
945 break;
946 }
947 }
948
949 let first_buffer = buffers[0].read(cx);
950 for buffer in &buffers[1..] {
951 let buffer = buffer.read(cx);
952 assert_eq!(
953 buffer.text(),
954 first_buffer.text(),
955 "Replica {} text != Replica 0 text",
956 buffer.replica_id()
957 );
958 }
959
960 for buffer in &buffers {
961 let buffer = buffer.read(cx).snapshot();
962 let expected_remote_selections = active_selections
963 .iter()
964 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
965 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
966 .collect::<Vec<_>>();
967 let actual_remote_selections = buffer
968 .remote_selections_in_range(Anchor::min()..Anchor::max())
969 .map(|(replica_id, selections)| (replica_id, selections.collect::<Vec<_>>()))
970 .collect::<Vec<_>>();
971 assert_eq!(actual_remote_selections, expected_remote_selections);
972 }
973}
974
975fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
976 buffer: &Buffer,
977 range: Range<T>,
978) -> Vec<(String, Option<DiagnosticSeverity>)> {
979 let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
980 for chunk in buffer.snapshot().chunks(range, Some(&Default::default())) {
981 if chunks
982 .last()
983 .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
984 {
985 chunks.last_mut().unwrap().0.push_str(chunk.text);
986 } else {
987 chunks.push((chunk.text.to_string(), chunk.diagnostic));
988 }
989 }
990 chunks
991}
992
993#[test]
994fn test_contiguous_ranges() {
995 assert_eq!(
996 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
997 &[1..4, 5..7, 9..13]
998 );
999
1000 // Respects the `max_len` parameter
1001 assert_eq!(
1002 contiguous_ranges(
1003 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
1004 3
1005 )
1006 .collect::<Vec<_>>(),
1007 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
1008 );
1009}
1010
1011impl Buffer {
1012 pub fn enclosing_bracket_point_ranges<T: ToOffset>(
1013 &self,
1014 range: Range<T>,
1015 ) -> Option<(Range<Point>, Range<Point>)> {
1016 self.snapshot()
1017 .enclosing_bracket_ranges(range)
1018 .map(|(start, end)| {
1019 let point_start = start.start.to_point(self)..start.end.to_point(self);
1020 let point_end = end.start.to_point(self)..end.end.to_point(self);
1021 (point_start, point_end)
1022 })
1023 }
1024}
1025
1026fn rust_lang() -> Language {
1027 Language::new(
1028 LanguageConfig {
1029 name: "Rust".to_string(),
1030 path_suffixes: vec!["rs".to_string()],
1031 language_server: None,
1032 ..Default::default()
1033 },
1034 Some(tree_sitter_rust::language()),
1035 )
1036 .with_indents_query(
1037 r#"
1038 (call_expression) @indent
1039 (field_expression) @indent
1040 (_ "(" ")" @end) @indent
1041 (_ "{" "}" @end) @indent
1042 "#,
1043 )
1044 .unwrap()
1045 .with_brackets_query(r#" ("{" @open "}" @close) "#)
1046 .unwrap()
1047}
1048
1049fn empty(point: Point) -> Range<Point> {
1050 point..point
1051}