1use super::*;
2use clock::ReplicaId;
3use collections::BTreeMap;
4use gpui::{ModelHandle, MutableAppContext};
5use proto::deserialize_operation;
6use rand::prelude::*;
7use settings::Settings;
8use std::{
9 cell::RefCell,
10 env,
11 ops::Range,
12 rc::Rc,
13 time::{Duration, Instant},
14};
15use text::network::Network;
16use unindent::Unindent as _;
17use util::post_inc;
18
19#[cfg(test)]
20#[ctor::ctor]
21fn init_logger() {
22 if std::env::var("RUST_LOG").is_ok() {
23 env_logger::init();
24 }
25}
26
27#[gpui::test]
28fn test_line_endings(cx: &mut gpui::MutableAppContext) {
29 cx.set_global(Settings::test(cx));
30 cx.add_model(|cx| {
31 let mut buffer =
32 Buffer::new(0, "one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx);
33 assert_eq!(buffer.text(), "one\ntwo\nthree");
34 assert_eq!(buffer.line_ending(), LineEnding::Windows);
35
36 buffer.check_invariants();
37 buffer.edit(
38 [(buffer.len()..buffer.len(), "\r\nfour")],
39 Some(AutoindentMode::EachLine),
40 cx,
41 );
42 buffer.edit([(0..0, "zero\r\n")], None, cx);
43 assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
44 assert_eq!(buffer.line_ending(), LineEnding::Windows);
45 buffer.check_invariants();
46
47 buffer
48 });
49}
50
51#[gpui::test]
52fn test_select_language() {
53 let registry = LanguageRegistry::test();
54 registry.add(Arc::new(Language::new(
55 LanguageConfig {
56 name: "Rust".into(),
57 path_suffixes: vec!["rs".to_string()],
58 ..Default::default()
59 },
60 Some(tree_sitter_rust::language()),
61 )));
62 registry.add(Arc::new(Language::new(
63 LanguageConfig {
64 name: "Make".into(),
65 path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
66 ..Default::default()
67 },
68 Some(tree_sitter_rust::language()),
69 )));
70
71 // matching file extension
72 assert_eq!(
73 registry.select_language("zed/lib.rs").map(|l| l.name()),
74 Some("Rust".into())
75 );
76 assert_eq!(
77 registry.select_language("zed/lib.mk").map(|l| l.name()),
78 Some("Make".into())
79 );
80
81 // matching filename
82 assert_eq!(
83 registry.select_language("zed/Makefile").map(|l| l.name()),
84 Some("Make".into())
85 );
86
87 // matching suffix that is not the full file extension or filename
88 assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
89 assert_eq!(
90 registry.select_language("zed/a.cars").map(|l| l.name()),
91 None
92 );
93 assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
94}
95
96#[gpui::test]
97fn test_edit_events(cx: &mut gpui::MutableAppContext) {
98 let mut now = Instant::now();
99 let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
100 let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
101
102 let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx));
103 let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx));
104 let buffer1_ops = Rc::new(RefCell::new(Vec::new()));
105 buffer1.update(cx, {
106 let buffer1_ops = buffer1_ops.clone();
107 |buffer, cx| {
108 let buffer_1_events = buffer_1_events.clone();
109 cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
110 Event::Operation(op) => buffer1_ops.borrow_mut().push(op),
111 event => buffer_1_events.borrow_mut().push(event),
112 })
113 .detach();
114 let buffer_2_events = buffer_2_events.clone();
115 cx.subscribe(&buffer2, move |_, _, event, _| {
116 buffer_2_events.borrow_mut().push(event.clone())
117 })
118 .detach();
119
120 // An edit emits an edited event, followed by a dirty changed event,
121 // since the buffer was previously in a clean state.
122 buffer.edit([(2..4, "XYZ")], None, cx);
123
124 // An empty transaction does not emit any events.
125 buffer.start_transaction();
126 buffer.end_transaction(cx);
127
128 // A transaction containing two edits emits one edited event.
129 now += Duration::from_secs(1);
130 buffer.start_transaction_at(now);
131 buffer.edit([(5..5, "u")], None, cx);
132 buffer.edit([(6..6, "w")], None, cx);
133 buffer.end_transaction_at(now, cx);
134
135 // Undoing a transaction emits one edited event.
136 buffer.undo(cx);
137 }
138 });
139
140 // Incorporating a set of remote ops emits a single edited event,
141 // followed by a dirty changed event.
142 buffer2.update(cx, |buffer, cx| {
143 buffer
144 .apply_ops(buffer1_ops.borrow_mut().drain(..), cx)
145 .unwrap();
146 });
147 assert_eq!(
148 mem::take(&mut *buffer_1_events.borrow_mut()),
149 vec![
150 Event::Edited,
151 Event::DirtyChanged,
152 Event::Edited,
153 Event::Edited,
154 ]
155 );
156 assert_eq!(
157 mem::take(&mut *buffer_2_events.borrow_mut()),
158 vec![Event::Edited, Event::DirtyChanged]
159 );
160
161 buffer1.update(cx, |buffer, cx| {
162 // Undoing the first transaction emits edited event, followed by a
163 // dirty changed event, since the buffer is again in a clean state.
164 buffer.undo(cx);
165 });
166 // Incorporating the remote ops again emits a single edited event,
167 // followed by a dirty changed event.
168 buffer2.update(cx, |buffer, cx| {
169 buffer
170 .apply_ops(buffer1_ops.borrow_mut().drain(..), cx)
171 .unwrap();
172 });
173 assert_eq!(
174 mem::take(&mut *buffer_1_events.borrow_mut()),
175 vec![Event::Edited, Event::DirtyChanged,]
176 );
177 assert_eq!(
178 mem::take(&mut *buffer_2_events.borrow_mut()),
179 vec![Event::Edited, Event::DirtyChanged]
180 );
181}
182
183#[gpui::test]
184async fn test_apply_diff(cx: &mut gpui::TestAppContext) {
185 let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
186 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
187 let anchor = buffer.read_with(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
188
189 let text = "a\nccc\ndddd\nffffff\n";
190 let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
191 buffer.update(cx, |buffer, cx| {
192 buffer.apply_diff(diff, cx).unwrap();
193 assert_eq!(buffer.text(), text);
194 assert_eq!(anchor.to_point(buffer), Point::new(2, 3));
195 });
196
197 let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
198 let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
199 buffer.update(cx, |buffer, cx| {
200 buffer.apply_diff(diff, cx).unwrap();
201 assert_eq!(buffer.text(), text);
202 assert_eq!(anchor.to_point(buffer), Point::new(4, 4));
203 });
204}
205
206#[gpui::test]
207async fn test_reparse(cx: &mut gpui::TestAppContext) {
208 let text = "fn a() {}";
209 let buffer =
210 cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx));
211
212 // Wait for the initial text to parse
213 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
214 assert_eq!(
215 get_tree_sexp(&buffer, cx),
216 concat!(
217 "(source_file (function_item name: (identifier) ",
218 "parameters: (parameters) ",
219 "body: (block)))"
220 )
221 );
222
223 buffer.update(cx, |buffer, _| {
224 buffer.set_sync_parse_timeout(Duration::ZERO)
225 });
226
227 // Perform some edits (add parameter and variable reference)
228 // Parsing doesn't begin until the transaction is complete
229 buffer.update(cx, |buf, cx| {
230 buf.start_transaction();
231
232 let offset = buf.text().find(')').unwrap();
233 buf.edit([(offset..offset, "b: C")], None, cx);
234 assert!(!buf.is_parsing());
235
236 let offset = buf.text().find('}').unwrap();
237 buf.edit([(offset..offset, " d; ")], None, cx);
238 assert!(!buf.is_parsing());
239
240 buf.end_transaction(cx);
241 assert_eq!(buf.text(), "fn a(b: C) { d; }");
242 assert!(buf.is_parsing());
243 });
244 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
245 assert_eq!(
246 get_tree_sexp(&buffer, cx),
247 concat!(
248 "(source_file (function_item name: (identifier) ",
249 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
250 "body: (block (expression_statement (identifier)))))"
251 )
252 );
253
254 // Perform a series of edits without waiting for the current parse to complete:
255 // * turn identifier into a field expression
256 // * turn field expression into a method call
257 // * add a turbofish to the method call
258 buffer.update(cx, |buf, cx| {
259 let offset = buf.text().find(';').unwrap();
260 buf.edit([(offset..offset, ".e")], None, cx);
261 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
262 assert!(buf.is_parsing());
263 });
264 buffer.update(cx, |buf, cx| {
265 let offset = buf.text().find(';').unwrap();
266 buf.edit([(offset..offset, "(f)")], None, cx);
267 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
268 assert!(buf.is_parsing());
269 });
270 buffer.update(cx, |buf, cx| {
271 let offset = buf.text().find("(f)").unwrap();
272 buf.edit([(offset..offset, "::<G>")], None, cx);
273 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
274 assert!(buf.is_parsing());
275 });
276 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
277 assert_eq!(
278 get_tree_sexp(&buffer, cx),
279 concat!(
280 "(source_file (function_item name: (identifier) ",
281 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
282 "body: (block (expression_statement (call_expression ",
283 "function: (generic_function ",
284 "function: (field_expression value: (identifier) field: (field_identifier)) ",
285 "type_arguments: (type_arguments (type_identifier))) ",
286 "arguments: (arguments (identifier)))))))",
287 )
288 );
289
290 buffer.update(cx, |buf, cx| {
291 buf.undo(cx);
292 assert_eq!(buf.text(), "fn a() {}");
293 assert!(buf.is_parsing());
294 });
295 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
296 assert_eq!(
297 get_tree_sexp(&buffer, cx),
298 concat!(
299 "(source_file (function_item name: (identifier) ",
300 "parameters: (parameters) ",
301 "body: (block)))"
302 )
303 );
304
305 buffer.update(cx, |buf, cx| {
306 buf.redo(cx);
307 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
308 assert!(buf.is_parsing());
309 });
310 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
311 assert_eq!(
312 get_tree_sexp(&buffer, cx),
313 concat!(
314 "(source_file (function_item name: (identifier) ",
315 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
316 "body: (block (expression_statement (call_expression ",
317 "function: (generic_function ",
318 "function: (field_expression value: (identifier) field: (field_identifier)) ",
319 "type_arguments: (type_arguments (type_identifier))) ",
320 "arguments: (arguments (identifier)))))))",
321 )
322 );
323}
324
325#[gpui::test]
326async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
327 let buffer = cx.add_model(|cx| {
328 let mut buffer = Buffer::new(0, "{}", cx).with_language(Arc::new(rust_lang()), cx);
329 buffer.set_sync_parse_timeout(Duration::ZERO);
330 buffer
331 });
332
333 // Wait for the initial text to parse
334 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
335 assert_eq!(
336 get_tree_sexp(&buffer, cx),
337 "(source_file (expression_statement (block)))"
338 );
339
340 buffer.update(cx, |buffer, cx| {
341 buffer.set_language(Some(Arc::new(json_lang())), cx)
342 });
343 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
344 assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
345}
346
347#[gpui::test]
348async fn test_outline(cx: &mut gpui::TestAppContext) {
349 let text = r#"
350 struct Person {
351 name: String,
352 age: usize,
353 }
354
355 mod module {
356 enum LoginState {
357 LoggedOut,
358 LoggingOn,
359 LoggedIn {
360 person: Person,
361 time: Instant,
362 }
363 }
364 }
365
366 impl Eq for Person {}
367
368 impl Drop for Person {
369 fn drop(&mut self) {
370 println!("bye");
371 }
372 }
373 "#
374 .unindent();
375
376 let buffer =
377 cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx));
378 let outline = buffer
379 .read_with(cx, |buffer, _| buffer.snapshot().outline(None))
380 .unwrap();
381
382 assert_eq!(
383 outline
384 .items
385 .iter()
386 .map(|item| (item.text.as_str(), item.depth))
387 .collect::<Vec<_>>(),
388 &[
389 ("struct Person", 0),
390 ("name", 1),
391 ("age", 1),
392 ("mod module", 0),
393 ("enum LoginState", 1),
394 ("LoggedOut", 2),
395 ("LoggingOn", 2),
396 ("LoggedIn", 2),
397 ("person", 3),
398 ("time", 3),
399 ("impl Eq for Person", 0),
400 ("impl Drop for Person", 0),
401 ("fn drop", 1),
402 ]
403 );
404
405 // Without space, we only match on names
406 assert_eq!(
407 search(&outline, "oon", cx).await,
408 &[
409 ("mod module", vec![]), // included as the parent of a match
410 ("enum LoginState", vec![]), // included as the parent of a match
411 ("LoggingOn", vec![1, 7, 8]), // matches
412 ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names
413 ]
414 );
415
416 assert_eq!(
417 search(&outline, "dp p", cx).await,
418 &[
419 ("impl Drop for Person", vec![5, 8, 9, 14]),
420 ("fn drop", vec![]),
421 ]
422 );
423 assert_eq!(
424 search(&outline, "dpn", cx).await,
425 &[("impl Drop for Person", vec![5, 14, 19])]
426 );
427 assert_eq!(
428 search(&outline, "impl ", cx).await,
429 &[
430 ("impl Eq for Person", vec![0, 1, 2, 3, 4]),
431 ("impl Drop for Person", vec![0, 1, 2, 3, 4]),
432 ("fn drop", vec![]),
433 ]
434 );
435
436 async fn search<'a>(
437 outline: &'a Outline<Anchor>,
438 query: &'a str,
439 cx: &'a gpui::TestAppContext,
440 ) -> Vec<(&'a str, Vec<usize>)> {
441 let matches = cx
442 .read(|cx| outline.search(query, cx.background().clone()))
443 .await;
444 matches
445 .into_iter()
446 .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions))
447 .collect::<Vec<_>>()
448 }
449}
450
451#[gpui::test]
452async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
453 let text = r#"
454 impl Person {
455 fn one() {
456 1
457 }
458
459 fn two() {
460 2
461 }fn three() {
462 3
463 }
464 }
465 "#
466 .unindent();
467
468 let buffer =
469 cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx));
470 let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
471
472 // point is at the start of an item
473 assert_eq!(
474 symbols_containing(Point::new(1, 4), &snapshot),
475 vec![
476 (
477 "impl Person".to_string(),
478 Point::new(0, 0)..Point::new(10, 1)
479 ),
480 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
481 ]
482 );
483
484 // point is in the middle of an item
485 assert_eq!(
486 symbols_containing(Point::new(2, 8), &snapshot),
487 vec![
488 (
489 "impl Person".to_string(),
490 Point::new(0, 0)..Point::new(10, 1)
491 ),
492 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
493 ]
494 );
495
496 // point is at the end of an item
497 assert_eq!(
498 symbols_containing(Point::new(3, 5), &snapshot),
499 vec![
500 (
501 "impl Person".to_string(),
502 Point::new(0, 0)..Point::new(10, 1)
503 ),
504 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
505 ]
506 );
507
508 // point is in between two adjacent items
509 assert_eq!(
510 symbols_containing(Point::new(7, 5), &snapshot),
511 vec![
512 (
513 "impl Person".to_string(),
514 Point::new(0, 0)..Point::new(10, 1)
515 ),
516 ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5))
517 ]
518 );
519
520 fn symbols_containing(
521 position: Point,
522 snapshot: &BufferSnapshot,
523 ) -> Vec<(String, Range<Point>)> {
524 snapshot
525 .symbols_containing(position, None)
526 .unwrap()
527 .into_iter()
528 .map(|item| {
529 (
530 item.text,
531 item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot),
532 )
533 })
534 .collect()
535 }
536}
537
538#[gpui::test]
539fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
540 cx.set_global(Settings::test(cx));
541 let buffer = cx.add_model(|cx| {
542 let text = "
543 mod x {
544 mod y {
545
546 }
547 }
548 "
549 .unindent();
550 Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx)
551 });
552 let buffer = buffer.read(cx);
553 assert_eq!(
554 buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)),
555 Some((
556 Point::new(0, 6)..Point::new(0, 7),
557 Point::new(4, 0)..Point::new(4, 1)
558 ))
559 );
560 assert_eq!(
561 buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)),
562 Some((
563 Point::new(1, 10)..Point::new(1, 11),
564 Point::new(3, 4)..Point::new(3, 5)
565 ))
566 );
567 assert_eq!(
568 buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)),
569 Some((
570 Point::new(1, 10)..Point::new(1, 11),
571 Point::new(3, 4)..Point::new(3, 5)
572 ))
573 );
574}
575
576#[gpui::test]
577fn test_range_for_syntax_ancestor(cx: &mut MutableAppContext) {
578 cx.add_model(|cx| {
579 let text = "fn a() { b(|c| {}) }";
580 let buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
581 let snapshot = buffer.snapshot();
582
583 assert_eq!(
584 snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")),
585 Some(range_of(text, "|"))
586 );
587 assert_eq!(
588 snapshot.range_for_syntax_ancestor(range_of(text, "|")),
589 Some(range_of(text, "|c|"))
590 );
591 assert_eq!(
592 snapshot.range_for_syntax_ancestor(range_of(text, "|c|")),
593 Some(range_of(text, "|c| {}"))
594 );
595 assert_eq!(
596 snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")),
597 Some(range_of(text, "(|c| {})"))
598 );
599
600 buffer
601 });
602
603 fn empty_range_at(text: &str, part: &str) -> Range<usize> {
604 let start = text.find(part).unwrap();
605 start..start
606 }
607
608 fn range_of(text: &str, part: &str) -> Range<usize> {
609 let start = text.find(part).unwrap();
610 start..start + part.len()
611 }
612}
613
614#[gpui::test]
615fn test_autoindent_with_soft_tabs(cx: &mut MutableAppContext) {
616 let settings = Settings::test(cx);
617 cx.set_global(settings);
618
619 cx.add_model(|cx| {
620 let text = "fn a() {}";
621 let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
622
623 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
624 assert_eq!(buffer.text(), "fn a() {\n \n}");
625
626 buffer.edit(
627 [(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
628 Some(AutoindentMode::EachLine),
629 cx,
630 );
631 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
632
633 // Create a field expression on a new line, causing that line
634 // to be indented.
635 buffer.edit(
636 [(Point::new(2, 4)..Point::new(2, 4), ".c")],
637 Some(AutoindentMode::EachLine),
638 cx,
639 );
640 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
641
642 // Remove the dot so that the line is no longer a field expression,
643 // causing the line to be outdented.
644 buffer.edit(
645 [(Point::new(2, 8)..Point::new(2, 9), "")],
646 Some(AutoindentMode::EachLine),
647 cx,
648 );
649 assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}");
650
651 buffer
652 });
653}
654
655#[gpui::test]
656fn test_autoindent_with_hard_tabs(cx: &mut MutableAppContext) {
657 let mut settings = Settings::test(cx);
658 settings.editor_overrides.hard_tabs = Some(true);
659 cx.set_global(settings);
660
661 cx.add_model(|cx| {
662 let text = "fn a() {}";
663 let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
664
665 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
666 assert_eq!(buffer.text(), "fn a() {\n\t\n}");
667
668 buffer.edit(
669 [(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
670 Some(AutoindentMode::EachLine),
671 cx,
672 );
673 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
674
675 // Create a field expression on a new line, causing that line
676 // to be indented.
677 buffer.edit(
678 [(Point::new(2, 1)..Point::new(2, 1), ".c")],
679 Some(AutoindentMode::EachLine),
680 cx,
681 );
682 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
683
684 // Remove the dot so that the line is no longer a field expression,
685 // causing the line to be outdented.
686 buffer.edit(
687 [(Point::new(2, 2)..Point::new(2, 3), "")],
688 Some(AutoindentMode::EachLine),
689 cx,
690 );
691 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
692
693 buffer
694 });
695}
696
697#[gpui::test]
698fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
699 let settings = Settings::test(cx);
700 cx.set_global(settings);
701
702 cx.add_model(|cx| {
703 let text = "
704 fn a() {
705 c;
706 d;
707 }
708 "
709 .unindent();
710
711 let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
712
713 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
714 // their indentation is not adjusted.
715 buffer.edit(
716 [
717 (empty(Point::new(1, 1)), "()"),
718 (empty(Point::new(2, 1)), "()"),
719 ],
720 Some(AutoindentMode::EachLine),
721 cx,
722 );
723 assert_eq!(
724 buffer.text(),
725 "
726 fn a() {
727 c();
728 d();
729 }
730 "
731 .unindent()
732 );
733
734 // When appending new content after these lines, the indentation is based on the
735 // preceding lines' actual indentation.
736 buffer.edit(
737 [
738 (empty(Point::new(1, 1)), "\n.f\n.g"),
739 (empty(Point::new(2, 1)), "\n.f\n.g"),
740 ],
741 Some(AutoindentMode::EachLine),
742 cx,
743 );
744 assert_eq!(
745 buffer.text(),
746 "
747 fn a() {
748 c
749 .f
750 .g();
751 d
752 .f
753 .g();
754 }
755 "
756 .unindent()
757 );
758 buffer
759 });
760
761 cx.add_model(|cx| {
762 let text = "
763 fn a() {
764 {
765 b()?
766 }
767 Ok(())
768 }
769 "
770 .unindent();
771 let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
772
773 // Delete a closing curly brace changes the suggested indent for the line.
774 buffer.edit(
775 [(Point::new(3, 4)..Point::new(3, 5), "")],
776 Some(AutoindentMode::EachLine),
777 cx,
778 );
779 assert_eq!(
780 buffer.text(),
781 "
782 fn a() {
783 {
784 b()?
785 |
786 Ok(())
787 }
788 "
789 .replace('|', "") // included in the string to preserve trailing whites
790 .unindent()
791 );
792
793 // Manually editing the leading whitespace
794 buffer.edit(
795 [(Point::new(3, 0)..Point::new(3, 12), "")],
796 Some(AutoindentMode::EachLine),
797 cx,
798 );
799 assert_eq!(
800 buffer.text(),
801 "
802 fn a() {
803 {
804 b()?
805
806 Ok(())
807 }
808 "
809 .unindent()
810 );
811 buffer
812 });
813}
814
815#[gpui::test]
816fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) {
817 cx.set_global(Settings::test(cx));
818 cx.add_model(|cx| {
819 let text = "
820 fn a() {}
821 "
822 .unindent();
823
824 let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
825
826 buffer.edit([(5..5, "\nb")], Some(AutoindentMode::EachLine), cx);
827 assert_eq!(
828 buffer.text(),
829 "
830 fn a(
831 b) {}
832 "
833 .unindent()
834 );
835
836 // The indentation suggestion changed because `@end` node (a close paren)
837 // is now at the beginning of the line.
838 buffer.edit(
839 [(Point::new(1, 4)..Point::new(1, 5), "")],
840 Some(AutoindentMode::EachLine),
841 cx,
842 );
843 assert_eq!(
844 buffer.text(),
845 "
846 fn a(
847 ) {}
848 "
849 .unindent()
850 );
851
852 buffer
853 });
854}
855
856#[gpui::test]
857fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut MutableAppContext) {
858 cx.set_global(Settings::test(cx));
859 cx.add_model(|cx| {
860 let text = "a\nb";
861 let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
862 buffer.edit(
863 [(0..1, "\n"), (2..3, "\n")],
864 Some(AutoindentMode::EachLine),
865 cx,
866 );
867 assert_eq!(buffer.text(), "\n\n\n");
868 buffer
869 });
870}
871
872#[gpui::test]
873fn test_autoindent_multi_line_insertion(cx: &mut MutableAppContext) {
874 cx.set_global(Settings::test(cx));
875 cx.add_model(|cx| {
876 let text = "
877 const a: usize = 1;
878 fn b() {
879 if c {
880 let d = 2;
881 }
882 }
883 "
884 .unindent();
885
886 let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
887 buffer.edit(
888 [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
889 Some(AutoindentMode::EachLine),
890 cx,
891 );
892 assert_eq!(
893 buffer.text(),
894 "
895 const a: usize = 1;
896 fn b() {
897 if c {
898 e(
899 f()
900 );
901 let d = 2;
902 }
903 }
904 "
905 .unindent()
906 );
907
908 buffer
909 });
910}
911
912#[gpui::test]
913fn test_autoindent_block_mode(cx: &mut MutableAppContext) {
914 cx.set_global(Settings::test(cx));
915 cx.add_model(|cx| {
916 let text = r#"
917 fn a() {
918 b();
919 }
920 "#
921 .unindent();
922 let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
923
924 let inserted_text = r#"
925 "
926 c
927 d
928 e
929 "
930 "#
931 .unindent();
932
933 // Insert the block at column zero. The entire block is indented
934 // so that the first line matches the previous line's indentation.
935 buffer.edit(
936 [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
937 Some(AutoindentMode::Block {
938 original_indent_columns: vec![0],
939 }),
940 cx,
941 );
942 assert_eq!(
943 buffer.text(),
944 r#"
945 fn a() {
946 b();
947 "
948 c
949 d
950 e
951 "
952 }
953 "#
954 .unindent()
955 );
956
957 // Insert the block at a deeper indent level. The entire block is outdented.
958 buffer.undo(cx);
959 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
960 buffer.edit(
961 [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
962 Some(AutoindentMode::Block {
963 original_indent_columns: vec![0],
964 }),
965 cx,
966 );
967 assert_eq!(
968 buffer.text(),
969 r#"
970 fn a() {
971 b();
972 "
973 c
974 d
975 e
976 "
977 }
978 "#
979 .unindent()
980 );
981
982 buffer
983 });
984}
985
986#[gpui::test]
987fn test_autoindent_language_without_indents_query(cx: &mut MutableAppContext) {
988 cx.set_global(Settings::test(cx));
989 cx.add_model(|cx| {
990 let text = "
991 * one
992 - a
993 - b
994 * two
995 "
996 .unindent();
997
998 let mut buffer = Buffer::new(0, text, cx).with_language(
999 Arc::new(Language::new(
1000 LanguageConfig {
1001 name: "Markdown".into(),
1002 auto_indent_using_last_non_empty_line: false,
1003 ..Default::default()
1004 },
1005 Some(tree_sitter_json::language()),
1006 )),
1007 cx,
1008 );
1009 buffer.edit(
1010 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
1011 Some(AutoindentMode::EachLine),
1012 cx,
1013 );
1014 assert_eq!(
1015 buffer.text(),
1016 "
1017 * one
1018 - a
1019 - b
1020
1021 * two
1022 "
1023 .unindent()
1024 );
1025 buffer
1026 });
1027}
1028
1029#[gpui::test]
1030fn test_serialization(cx: &mut gpui::MutableAppContext) {
1031 let mut now = Instant::now();
1032
1033 let buffer1 = cx.add_model(|cx| {
1034 let mut buffer = Buffer::new(0, "abc", cx);
1035 buffer.edit([(3..3, "D")], None, cx);
1036
1037 now += Duration::from_secs(1);
1038 buffer.start_transaction_at(now);
1039 buffer.edit([(4..4, "E")], None, cx);
1040 buffer.end_transaction_at(now, cx);
1041 assert_eq!(buffer.text(), "abcDE");
1042
1043 buffer.undo(cx);
1044 assert_eq!(buffer.text(), "abcD");
1045
1046 buffer.edit([(4..4, "F")], None, cx);
1047 assert_eq!(buffer.text(), "abcDF");
1048 buffer
1049 });
1050 assert_eq!(buffer1.read(cx).text(), "abcDF");
1051
1052 let state = buffer1.read(cx).to_proto();
1053 let ops = cx.background().block(buffer1.read(cx).serialize_ops(cx));
1054 let buffer2 = cx.add_model(|cx| {
1055 let mut buffer = Buffer::from_proto(1, state, None).unwrap();
1056 buffer
1057 .apply_ops(
1058 ops.into_iter()
1059 .map(|op| proto::deserialize_operation(op).unwrap()),
1060 cx,
1061 )
1062 .unwrap();
1063 buffer
1064 });
1065 assert_eq!(buffer2.read(cx).text(), "abcDF");
1066}
1067
1068#[gpui::test(iterations = 100)]
1069fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
1070 let min_peers = env::var("MIN_PEERS")
1071 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
1072 .unwrap_or(1);
1073 let max_peers = env::var("MAX_PEERS")
1074 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
1075 .unwrap_or(5);
1076 let operations = env::var("OPERATIONS")
1077 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1078 .unwrap_or(10);
1079
1080 let base_text_len = rng.gen_range(0..10);
1081 let base_text = RandomCharIter::new(&mut rng)
1082 .take(base_text_len)
1083 .collect::<String>();
1084 let mut replica_ids = Vec::new();
1085 let mut buffers = Vec::new();
1086 let network = Rc::new(RefCell::new(Network::new(rng.clone())));
1087 let base_buffer = cx.add_model(|cx| Buffer::new(0, base_text.as_str(), cx));
1088
1089 for i in 0..rng.gen_range(min_peers..=max_peers) {
1090 let buffer = cx.add_model(|cx| {
1091 let state = base_buffer.read(cx).to_proto();
1092 let ops = cx
1093 .background()
1094 .block(base_buffer.read(cx).serialize_ops(cx));
1095 let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap();
1096 buffer
1097 .apply_ops(
1098 ops.into_iter()
1099 .map(|op| proto::deserialize_operation(op).unwrap()),
1100 cx,
1101 )
1102 .unwrap();
1103 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
1104 let network = network.clone();
1105 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
1106 if let Event::Operation(op) = event {
1107 network
1108 .borrow_mut()
1109 .broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]);
1110 }
1111 })
1112 .detach();
1113 buffer
1114 });
1115 buffers.push(buffer);
1116 replica_ids.push(i as ReplicaId);
1117 network.borrow_mut().add_peer(i as ReplicaId);
1118 log::info!("Adding initial peer with replica id {}", i);
1119 }
1120
1121 log::info!("initial text: {:?}", base_text);
1122
1123 let mut now = Instant::now();
1124 let mut mutation_count = operations;
1125 let mut next_diagnostic_id = 0;
1126 let mut active_selections = BTreeMap::default();
1127 loop {
1128 let replica_index = rng.gen_range(0..replica_ids.len());
1129 let replica_id = replica_ids[replica_index];
1130 let buffer = &mut buffers[replica_index];
1131 let mut new_buffer = None;
1132 match rng.gen_range(0..100) {
1133 0..=29 if mutation_count != 0 => {
1134 buffer.update(cx, |buffer, cx| {
1135 buffer.start_transaction_at(now);
1136 buffer.randomly_edit(&mut rng, 5, cx);
1137 buffer.end_transaction_at(now, cx);
1138 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
1139 });
1140 mutation_count -= 1;
1141 }
1142 30..=39 if mutation_count != 0 => {
1143 buffer.update(cx, |buffer, cx| {
1144 let mut selections = Vec::new();
1145 for id in 0..rng.gen_range(1..=5) {
1146 let range = buffer.random_byte_range(0, &mut rng);
1147 selections.push(Selection {
1148 id,
1149 start: buffer.anchor_before(range.start),
1150 end: buffer.anchor_before(range.end),
1151 reversed: false,
1152 goal: SelectionGoal::None,
1153 });
1154 }
1155 let selections: Arc<[Selection<Anchor>]> = selections.into();
1156 log::info!(
1157 "peer {} setting active selections: {:?}",
1158 replica_id,
1159 selections
1160 );
1161 active_selections.insert(replica_id, selections.clone());
1162 buffer.set_active_selections(selections, false, cx);
1163 });
1164 mutation_count -= 1;
1165 }
1166 40..=49 if mutation_count != 0 && replica_id == 0 => {
1167 let entry_count = rng.gen_range(1..=5);
1168 buffer.update(cx, |buffer, cx| {
1169 let diagnostics = DiagnosticSet::new(
1170 (0..entry_count).map(|_| {
1171 let range = buffer.random_byte_range(0, &mut rng);
1172 let range = range.to_point_utf16(buffer);
1173 DiagnosticEntry {
1174 range,
1175 diagnostic: Diagnostic {
1176 message: post_inc(&mut next_diagnostic_id).to_string(),
1177 ..Default::default()
1178 },
1179 }
1180 }),
1181 buffer,
1182 );
1183 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
1184 buffer.update_diagnostics(diagnostics, cx);
1185 });
1186 mutation_count -= 1;
1187 }
1188 50..=59 if replica_ids.len() < max_peers => {
1189 let old_buffer_state = buffer.read(cx).to_proto();
1190 let old_buffer_ops = cx.background().block(buffer.read(cx).serialize_ops(cx));
1191 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
1192 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
1193 .choose(&mut rng)
1194 .unwrap();
1195 log::info!(
1196 "Adding new replica {} (replicating from {})",
1197 new_replica_id,
1198 replica_id
1199 );
1200 new_buffer = Some(cx.add_model(|cx| {
1201 let mut new_buffer =
1202 Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap();
1203 new_buffer
1204 .apply_ops(
1205 old_buffer_ops
1206 .into_iter()
1207 .map(|op| deserialize_operation(op).unwrap()),
1208 cx,
1209 )
1210 .unwrap();
1211 log::info!(
1212 "New replica {} text: {:?}",
1213 new_buffer.replica_id(),
1214 new_buffer.text()
1215 );
1216 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
1217 let network = network.clone();
1218 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
1219 if let Event::Operation(op) = event {
1220 network.borrow_mut().broadcast(
1221 buffer.replica_id(),
1222 vec![proto::serialize_operation(op)],
1223 );
1224 }
1225 })
1226 .detach();
1227 new_buffer
1228 }));
1229 network.borrow_mut().replicate(replica_id, new_replica_id);
1230
1231 if new_replica_id as usize == replica_ids.len() {
1232 replica_ids.push(new_replica_id);
1233 } else {
1234 let new_buffer = new_buffer.take().unwrap();
1235 while network.borrow().has_unreceived(new_replica_id) {
1236 let ops = network
1237 .borrow_mut()
1238 .receive(new_replica_id)
1239 .into_iter()
1240 .map(|op| proto::deserialize_operation(op).unwrap());
1241 if ops.len() > 0 {
1242 log::info!(
1243 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
1244 new_replica_id,
1245 buffer.read(cx).version(),
1246 ops.len(),
1247 ops
1248 );
1249 new_buffer.update(cx, |new_buffer, cx| {
1250 new_buffer.apply_ops(ops, cx).unwrap();
1251 });
1252 }
1253 }
1254 buffers[new_replica_id as usize] = new_buffer;
1255 }
1256 }
1257 60..=69 if mutation_count != 0 => {
1258 buffer.update(cx, |buffer, cx| {
1259 buffer.randomly_undo_redo(&mut rng, cx);
1260 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
1261 });
1262 mutation_count -= 1;
1263 }
1264 _ if network.borrow().has_unreceived(replica_id) => {
1265 let ops = network
1266 .borrow_mut()
1267 .receive(replica_id)
1268 .into_iter()
1269 .map(|op| proto::deserialize_operation(op).unwrap());
1270 if ops.len() > 0 {
1271 log::info!(
1272 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
1273 replica_id,
1274 buffer.read(cx).version(),
1275 ops.len(),
1276 ops
1277 );
1278 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
1279 }
1280 }
1281 _ => {}
1282 }
1283
1284 now += Duration::from_millis(rng.gen_range(0..=200));
1285 buffers.extend(new_buffer);
1286
1287 for buffer in &buffers {
1288 buffer.read(cx).check_invariants();
1289 }
1290
1291 if mutation_count == 0 && network.borrow().is_idle() {
1292 break;
1293 }
1294 }
1295
1296 let first_buffer = buffers[0].read(cx).snapshot();
1297 for buffer in &buffers[1..] {
1298 let buffer = buffer.read(cx).snapshot();
1299 assert_eq!(
1300 buffer.version(),
1301 first_buffer.version(),
1302 "Replica {} version != Replica 0 version",
1303 buffer.replica_id()
1304 );
1305 assert_eq!(
1306 buffer.text(),
1307 first_buffer.text(),
1308 "Replica {} text != Replica 0 text",
1309 buffer.replica_id()
1310 );
1311 assert_eq!(
1312 buffer
1313 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
1314 .collect::<Vec<_>>(),
1315 first_buffer
1316 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
1317 .collect::<Vec<_>>(),
1318 "Replica {} diagnostics != Replica 0 diagnostics",
1319 buffer.replica_id()
1320 );
1321 }
1322
1323 for buffer in &buffers {
1324 let buffer = buffer.read(cx).snapshot();
1325 let actual_remote_selections = buffer
1326 .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
1327 .map(|(replica_id, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
1328 .collect::<Vec<_>>();
1329 let expected_remote_selections = active_selections
1330 .iter()
1331 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
1332 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
1333 .collect::<Vec<_>>();
1334 assert_eq!(
1335 actual_remote_selections,
1336 expected_remote_selections,
1337 "Replica {} remote selections != expected selections",
1338 buffer.replica_id()
1339 );
1340 }
1341}
1342
1343#[test]
1344fn test_contiguous_ranges() {
1345 assert_eq!(
1346 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
1347 &[1..4, 5..7, 9..13]
1348 );
1349
1350 // Respects the `max_len` parameter
1351 assert_eq!(
1352 contiguous_ranges(
1353 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
1354 3
1355 )
1356 .collect::<Vec<_>>(),
1357 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
1358 );
1359}
1360
1361impl Buffer {
1362 pub fn enclosing_bracket_point_ranges<T: ToOffset>(
1363 &self,
1364 range: Range<T>,
1365 ) -> Option<(Range<Point>, Range<Point>)> {
1366 self.snapshot()
1367 .enclosing_bracket_ranges(range)
1368 .map(|(start, end)| {
1369 let point_start = start.start.to_point(self)..start.end.to_point(self);
1370 let point_end = end.start.to_point(self)..end.end.to_point(self);
1371 (point_start, point_end)
1372 })
1373 }
1374}
1375
1376fn rust_lang() -> Language {
1377 Language::new(
1378 LanguageConfig {
1379 name: "Rust".into(),
1380 path_suffixes: vec!["rs".to_string()],
1381 ..Default::default()
1382 },
1383 Some(tree_sitter_rust::language()),
1384 )
1385 .with_indents_query(
1386 r#"
1387 (call_expression) @indent
1388 (field_expression) @indent
1389 (_ "(" ")" @end) @indent
1390 (_ "{" "}" @end) @indent
1391 "#,
1392 )
1393 .unwrap()
1394 .with_brackets_query(
1395 r#"
1396 ("{" @open "}" @close)
1397 "#,
1398 )
1399 .unwrap()
1400 .with_outline_query(
1401 r#"
1402 (struct_item
1403 "struct" @context
1404 name: (_) @name) @item
1405 (enum_item
1406 "enum" @context
1407 name: (_) @name) @item
1408 (enum_variant
1409 name: (_) @name) @item
1410 (field_declaration
1411 name: (_) @name) @item
1412 (impl_item
1413 "impl" @context
1414 trait: (_)? @name
1415 "for"? @context
1416 type: (_) @name) @item
1417 (function_item
1418 "fn" @context
1419 name: (_) @name) @item
1420 (mod_item
1421 "mod" @context
1422 name: (_) @name) @item
1423 "#,
1424 )
1425 .unwrap()
1426}
1427
1428fn json_lang() -> Language {
1429 Language::new(
1430 LanguageConfig {
1431 name: "Json".into(),
1432 path_suffixes: vec!["js".to_string()],
1433 ..Default::default()
1434 },
1435 Some(tree_sitter_json::language()),
1436 )
1437}
1438
1439fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
1440 buffer.read_with(cx, |buffer, _| {
1441 let snapshot = buffer.snapshot();
1442 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
1443 layers[0].2.to_sexp()
1444 })
1445}
1446
1447fn empty(point: Point) -> Range<Point> {
1448 point..point
1449}