1use super::*;
2use crate::Buffer;
3use crate::language_settings::{
4 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent,
5};
6use clock::ReplicaId;
7use collections::BTreeMap;
8use futures::FutureExt as _;
9use gpui::{App, AppContext as _, BorrowAppContext, Entity};
10use gpui::{HighlightStyle, TestAppContext};
11use indoc::indoc;
12use proto::deserialize_operation;
13use rand::prelude::*;
14use regex::RegexBuilder;
15use settings::SettingsStore;
16use std::collections::BTreeSet;
17use std::{
18 env,
19 ops::Range,
20 sync::LazyLock,
21 time::{Duration, Instant},
22};
23use syntax_map::TreeSitterOptions;
24use text::network::Network;
25use text::{BufferId, LineEnding};
26use text::{Point, ToPoint};
27use theme::ActiveTheme;
28use unindent::Unindent as _;
29use util::test::marked_text_offsets;
30use util::{RandomCharIter, assert_set_eq, post_inc, test::marked_text_ranges};
31
32pub static TRAILING_WHITESPACE_REGEX: LazyLock<regex::Regex> = LazyLock::new(|| {
33 RegexBuilder::new(r"[ \t]+$")
34 .multi_line(true)
35 .build()
36 .expect("Failed to create TRAILING_WHITESPACE_REGEX")
37});
38
39#[cfg(test)]
40#[ctor::ctor]
41fn init_logger() {
42 zlog::init_test();
43}
44
45#[gpui::test]
46fn test_line_endings(cx: &mut gpui::App) {
47 init_settings(cx, |_| {});
48
49 cx.new(|cx| {
50 let mut buffer =
51 Buffer::local("one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx);
52 assert_eq!(buffer.text(), "one\ntwo\nthree");
53 assert_eq!(buffer.line_ending(), LineEnding::Windows);
54
55 buffer.check_invariants();
56 buffer.edit(
57 [(buffer.len()..buffer.len(), "\r\nfour")],
58 Some(AutoindentMode::EachLine),
59 cx,
60 );
61 buffer.edit([(0..0, "zero\r\n")], None, cx);
62 assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
63 assert_eq!(buffer.line_ending(), LineEnding::Windows);
64 buffer.check_invariants();
65
66 buffer
67 });
68}
69
70#[gpui::test]
71fn test_set_line_ending(cx: &mut TestAppContext) {
72 let base = cx.new(|cx| Buffer::local("one\ntwo\nthree\n", cx));
73 let base_replica = cx.new(|cx| {
74 Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
75 });
76 base.update(cx, |_buffer, cx| {
77 cx.subscribe(&base_replica, |this, _, event, cx| {
78 if let BufferEvent::Operation {
79 operation,
80 is_local: true,
81 } = event
82 {
83 this.apply_ops([operation.clone()], cx);
84 }
85 })
86 .detach();
87 });
88 base_replica.update(cx, |_buffer, cx| {
89 cx.subscribe(&base, |this, _, event, cx| {
90 if let BufferEvent::Operation {
91 operation,
92 is_local: true,
93 } = event
94 {
95 this.apply_ops([operation.clone()], cx);
96 }
97 })
98 .detach();
99 });
100
101 // Base
102 base_replica.read_with(cx, |buffer, _| {
103 assert_eq!(buffer.line_ending(), LineEnding::Unix);
104 });
105 base.update(cx, |buffer, cx| {
106 assert_eq!(buffer.line_ending(), LineEnding::Unix);
107 buffer.set_line_ending(LineEnding::Windows, cx);
108 assert_eq!(buffer.line_ending(), LineEnding::Windows);
109 });
110 base_replica.read_with(cx, |buffer, _| {
111 assert_eq!(buffer.line_ending(), LineEnding::Windows);
112 });
113 base.update(cx, |buffer, cx| {
114 buffer.set_line_ending(LineEnding::Unix, cx);
115 assert_eq!(buffer.line_ending(), LineEnding::Unix);
116 });
117 base_replica.read_with(cx, |buffer, _| {
118 assert_eq!(buffer.line_ending(), LineEnding::Unix);
119 });
120
121 // Replica
122 base.read_with(cx, |buffer, _| {
123 assert_eq!(buffer.line_ending(), LineEnding::Unix);
124 });
125 base_replica.update(cx, |buffer, cx| {
126 assert_eq!(buffer.line_ending(), LineEnding::Unix);
127 buffer.set_line_ending(LineEnding::Windows, cx);
128 assert_eq!(buffer.line_ending(), LineEnding::Windows);
129 });
130 base.read_with(cx, |buffer, _| {
131 assert_eq!(buffer.line_ending(), LineEnding::Windows);
132 });
133 base_replica.update(cx, |buffer, cx| {
134 buffer.set_line_ending(LineEnding::Unix, cx);
135 assert_eq!(buffer.line_ending(), LineEnding::Unix);
136 });
137 base.read_with(cx, |buffer, _| {
138 assert_eq!(buffer.line_ending(), LineEnding::Unix);
139 });
140}
141
142#[gpui::test]
143fn test_select_language(cx: &mut App) {
144 init_settings(cx, |_| {});
145
146 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
147 registry.add(Arc::new(Language::new(
148 LanguageConfig {
149 name: LanguageName::new("Rust"),
150 matcher: LanguageMatcher {
151 path_suffixes: vec!["rs".to_string()],
152 ..Default::default()
153 },
154 ..Default::default()
155 },
156 Some(tree_sitter_rust::LANGUAGE.into()),
157 )));
158 registry.add(Arc::new(Language::new(
159 LanguageConfig {
160 name: "Rust with longer extension".into(),
161 matcher: LanguageMatcher {
162 path_suffixes: vec!["longer.rs".to_string()],
163 ..Default::default()
164 },
165 ..Default::default()
166 },
167 Some(tree_sitter_rust::LANGUAGE.into()),
168 )));
169 registry.add(Arc::new(Language::new(
170 LanguageConfig {
171 name: LanguageName::new("Make"),
172 matcher: LanguageMatcher {
173 path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
174 ..Default::default()
175 },
176 ..Default::default()
177 },
178 Some(tree_sitter_rust::LANGUAGE.into()),
179 )));
180
181 // matching file extension
182 assert_eq!(
183 registry
184 .language_for_file(&file("src/lib.rs"), None, cx)
185 .map(|l| l.name()),
186 Some("Rust".into())
187 );
188 assert_eq!(
189 registry
190 .language_for_file(&file("src/lib.mk"), None, cx)
191 .map(|l| l.name()),
192 Some("Make".into())
193 );
194
195 // matching longer, compound extension, part of which could also match another lang
196 assert_eq!(
197 registry
198 .language_for_file(&file("src/lib.longer.rs"), None, cx)
199 .map(|l| l.name()),
200 Some("Rust with longer extension".into())
201 );
202
203 // matching filename
204 assert_eq!(
205 registry
206 .language_for_file(&file("src/Makefile"), None, cx)
207 .map(|l| l.name()),
208 Some("Make".into())
209 );
210
211 // matching suffix that is not the full file extension or filename
212 assert_eq!(
213 registry
214 .language_for_file(&file("zed/cars"), None, cx)
215 .map(|l| l.name()),
216 None
217 );
218 assert_eq!(
219 registry
220 .language_for_file(&file("zed/a.cars"), None, cx)
221 .map(|l| l.name()),
222 None
223 );
224 assert_eq!(
225 registry
226 .language_for_file(&file("zed/sumk"), None, cx)
227 .map(|l| l.name()),
228 None
229 );
230}
231
232#[gpui::test(iterations = 10)]
233async fn test_first_line_pattern(cx: &mut TestAppContext) {
234 cx.update(|cx| init_settings(cx, |_| {}));
235
236 let languages = LanguageRegistry::test(cx.executor());
237 let languages = Arc::new(languages);
238
239 languages.register_test_language(LanguageConfig {
240 name: "JavaScript".into(),
241 matcher: LanguageMatcher {
242 path_suffixes: vec!["js".into()],
243 first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
244 },
245 ..Default::default()
246 });
247
248 assert!(
249 cx.read(|cx| languages.language_for_file(&file("the/script"), None, cx))
250 .is_none()
251 );
252 assert!(
253 cx.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
254 .is_none()
255 );
256
257 assert_eq!(
258 cx.read(|cx| languages.language_for_file(
259 &file("the/script"),
260 Some(&"#!/bin/env node".into()),
261 cx
262 ))
263 .unwrap()
264 .name(),
265 "JavaScript".into()
266 );
267}
268
269#[gpui::test]
270async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) {
271 cx.update(|cx| {
272 init_settings(cx, |settings| {
273 settings.file_types.extend([
274 ("TypeScript".into(), vec!["js".into()]),
275 (
276 "JavaScript".into(),
277 vec!["*longer.ts".into(), "ecmascript".into()],
278 ),
279 ("C++".into(), vec!["c".into(), "*.dev".into()]),
280 (
281 "Dockerfile".into(),
282 vec!["Dockerfile".into(), "Dockerfile.*".into()],
283 ),
284 ]);
285 })
286 });
287
288 let languages = Arc::new(LanguageRegistry::test(cx.executor()));
289
290 for config in [
291 LanguageConfig {
292 name: "JavaScript".into(),
293 matcher: LanguageMatcher {
294 path_suffixes: vec!["js".to_string()],
295 ..Default::default()
296 },
297 ..Default::default()
298 },
299 LanguageConfig {
300 name: "TypeScript".into(),
301 matcher: LanguageMatcher {
302 path_suffixes: vec!["ts".to_string(), "ts.ecmascript".to_string()],
303 ..Default::default()
304 },
305 ..Default::default()
306 },
307 LanguageConfig {
308 name: "C++".into(),
309 matcher: LanguageMatcher {
310 path_suffixes: vec!["cpp".to_string()],
311 ..Default::default()
312 },
313 ..Default::default()
314 },
315 LanguageConfig {
316 name: "C".into(),
317 matcher: LanguageMatcher {
318 path_suffixes: vec!["c".to_string()],
319 ..Default::default()
320 },
321 ..Default::default()
322 },
323 LanguageConfig {
324 name: "Dockerfile".into(),
325 matcher: LanguageMatcher {
326 path_suffixes: vec!["Dockerfile".to_string()],
327 ..Default::default()
328 },
329 ..Default::default()
330 },
331 ] {
332 languages.add(Arc::new(Language::new(config, None)));
333 }
334
335 // matches system-provided lang extension
336 let language = cx
337 .read(|cx| languages.language_for_file(&file("foo.ts"), None, cx))
338 .unwrap();
339 assert_eq!(language.name(), "TypeScript".into());
340 let language = cx
341 .read(|cx| languages.language_for_file(&file("foo.ts.ecmascript"), None, cx))
342 .unwrap();
343 assert_eq!(language.name(), "TypeScript".into());
344 let language = cx
345 .read(|cx| languages.language_for_file(&file("foo.cpp"), None, cx))
346 .unwrap();
347 assert_eq!(language.name(), "C++".into());
348
349 // user configured lang extension, same length as system-provided
350 let language = cx
351 .read(|cx| languages.language_for_file(&file("foo.js"), None, cx))
352 .unwrap();
353 assert_eq!(language.name(), "TypeScript".into());
354 let language = cx
355 .read(|cx| languages.language_for_file(&file("foo.c"), None, cx))
356 .unwrap();
357 assert_eq!(language.name(), "C++".into());
358
359 // user configured lang extension, longer than system-provided
360 let language = cx
361 .read(|cx| languages.language_for_file(&file("foo.longer.ts"), None, cx))
362 .unwrap();
363 assert_eq!(language.name(), "JavaScript".into());
364
365 // user configured lang extension, shorter than system-provided
366 let language = cx
367 .read(|cx| languages.language_for_file(&file("foo.ecmascript"), None, cx))
368 .unwrap();
369 assert_eq!(language.name(), "JavaScript".into());
370
371 // user configured glob matches
372 let language = cx
373 .read(|cx| languages.language_for_file(&file("c-plus-plus.dev"), None, cx))
374 .unwrap();
375 assert_eq!(language.name(), "C++".into());
376 // should match Dockerfile.* => Dockerfile, not *.dev => C++
377 let language = cx
378 .read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx))
379 .unwrap();
380 assert_eq!(language.name(), "Dockerfile".into());
381}
382
383fn file(path: &str) -> Arc<dyn File> {
384 Arc::new(TestFile {
385 path: Path::new(path).into(),
386 root_name: "zed".into(),
387 local_root: None,
388 })
389}
390
391#[gpui::test]
392fn test_edit_events(cx: &mut gpui::App) {
393 let mut now = Instant::now();
394 let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
395 let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
396
397 let buffer1 = cx.new(|cx| Buffer::local("abcdef", cx));
398 let buffer2 = cx.new(|cx| {
399 Buffer::remote(
400 BufferId::from(cx.entity_id().as_non_zero_u64()),
401 1,
402 Capability::ReadWrite,
403 "abcdef",
404 )
405 });
406 let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
407 buffer1.update(cx, {
408 let buffer1_ops = buffer1_ops.clone();
409 |buffer, cx| {
410 let buffer_1_events = buffer_1_events.clone();
411 cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
412 BufferEvent::Operation {
413 operation,
414 is_local: true,
415 } => buffer1_ops.lock().push(operation),
416 event => buffer_1_events.lock().push(event),
417 })
418 .detach();
419 let buffer_2_events = buffer_2_events.clone();
420 cx.subscribe(&buffer2, move |_, _, event, _| match event.clone() {
421 BufferEvent::Operation {
422 is_local: false, ..
423 } => {}
424 event => buffer_2_events.lock().push(event),
425 })
426 .detach();
427
428 // An edit emits an edited event, followed by a dirty changed event,
429 // since the buffer was previously in a clean state.
430 buffer.edit([(2..4, "XYZ")], None, cx);
431
432 // An empty transaction does not emit any events.
433 buffer.start_transaction();
434 buffer.end_transaction(cx);
435
436 // A transaction containing two edits emits one edited event.
437 now += Duration::from_secs(1);
438 buffer.start_transaction_at(now);
439 buffer.edit([(5..5, "u")], None, cx);
440 buffer.edit([(6..6, "w")], None, cx);
441 buffer.end_transaction_at(now, cx);
442
443 // Undoing a transaction emits one edited event.
444 buffer.undo(cx);
445 }
446 });
447
448 // Incorporating a set of remote ops emits a single edited event,
449 // followed by a dirty changed event.
450 buffer2.update(cx, |buffer, cx| {
451 buffer.apply_ops(buffer1_ops.lock().drain(..), cx);
452 });
453 assert_eq!(
454 mem::take(&mut *buffer_1_events.lock()),
455 vec![
456 BufferEvent::Edited,
457 BufferEvent::DirtyChanged,
458 BufferEvent::Edited,
459 BufferEvent::Edited,
460 ]
461 );
462 assert_eq!(
463 mem::take(&mut *buffer_2_events.lock()),
464 vec![BufferEvent::Edited, BufferEvent::DirtyChanged]
465 );
466
467 buffer1.update(cx, |buffer, cx| {
468 // Undoing the first transaction emits edited event, followed by a
469 // dirty changed event, since the buffer is again in a clean state.
470 buffer.undo(cx);
471 });
472 // Incorporating the remote ops again emits a single edited event,
473 // followed by a dirty changed event.
474 buffer2.update(cx, |buffer, cx| {
475 buffer.apply_ops(buffer1_ops.lock().drain(..), cx);
476 });
477 assert_eq!(
478 mem::take(&mut *buffer_1_events.lock()),
479 vec![BufferEvent::Edited, BufferEvent::DirtyChanged,]
480 );
481 assert_eq!(
482 mem::take(&mut *buffer_2_events.lock()),
483 vec![BufferEvent::Edited, BufferEvent::DirtyChanged]
484 );
485}
486
487#[gpui::test]
488async fn test_apply_diff(cx: &mut TestAppContext) {
489 let (text, offsets) = marked_text_offsets(
490 "one two three\nfour fiˇve six\nseven eightˇ nine\nten eleven twelve\n",
491 );
492 let buffer = cx.new(|cx| Buffer::local(text, cx));
493 let anchors = buffer.update(cx, |buffer, _| {
494 offsets
495 .iter()
496 .map(|offset| buffer.anchor_before(offset))
497 .collect::<Vec<_>>()
498 });
499
500 let (text, offsets) = marked_text_offsets(
501 "one two three\n{\nfour FIVEˇ six\n}\nseven AND EIGHTˇ nine\nten eleven twelve\n",
502 );
503
504 let diff = buffer.update(cx, |b, cx| b.diff(text.clone(), cx)).await;
505 buffer.update(cx, |buffer, cx| {
506 buffer.apply_diff(diff, cx).unwrap();
507 assert_eq!(buffer.text(), text);
508 let actual_offsets = anchors
509 .iter()
510 .map(|anchor| anchor.to_offset(buffer))
511 .collect::<Vec<_>>();
512 assert_eq!(actual_offsets, offsets);
513 });
514
515 let (text, offsets) =
516 marked_text_offsets("one two three\n{\nˇ}\nseven AND EIGHTEENˇ nine\nten eleven twelve\n");
517
518 let diff = buffer.update(cx, |b, cx| b.diff(text.clone(), cx)).await;
519 buffer.update(cx, |buffer, cx| {
520 buffer.apply_diff(diff, cx).unwrap();
521 assert_eq!(buffer.text(), text);
522 let actual_offsets = anchors
523 .iter()
524 .map(|anchor| anchor.to_offset(buffer))
525 .collect::<Vec<_>>();
526 assert_eq!(actual_offsets, offsets);
527 });
528}
529
530#[gpui::test(iterations = 10)]
531async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
532 let text = [
533 "zero", //
534 "one ", // 2 trailing spaces
535 "two", //
536 "three ", // 3 trailing spaces
537 "four", //
538 "five ", // 4 trailing spaces
539 ]
540 .join("\n");
541
542 let buffer = cx.new(|cx| Buffer::local(text, cx));
543
544 // Spawn a task to format the buffer's whitespace.
545 // Pause so that the formatting task starts running.
546 let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
547 smol::future::yield_now().await;
548
549 // Edit the buffer while the normalization task is running.
550 let version_before_edit = buffer.update(cx, |buffer, _| buffer.version());
551 buffer.update(cx, |buffer, cx| {
552 buffer.edit(
553 [
554 (Point::new(0, 1)..Point::new(0, 1), "EE"),
555 (Point::new(3, 5)..Point::new(3, 5), "EEE"),
556 ],
557 None,
558 cx,
559 );
560 });
561
562 let format_diff = format.await;
563 buffer.update(cx, |buffer, cx| {
564 let version_before_format = format_diff.base_version.clone();
565 buffer.apply_diff(format_diff, cx);
566
567 // The outcome depends on the order of concurrent tasks.
568 //
569 // If the edit occurred while searching for trailing whitespace ranges,
570 // then the trailing whitespace region touched by the edit is left intact.
571 if version_before_format == version_before_edit {
572 assert_eq!(
573 buffer.text(),
574 [
575 "zEEero", //
576 "one", //
577 "two", //
578 "threeEEE ", //
579 "four", //
580 "five", //
581 ]
582 .join("\n")
583 );
584 }
585 // Otherwise, all trailing whitespace is removed.
586 else {
587 assert_eq!(
588 buffer.text(),
589 [
590 "zEEero", //
591 "one", //
592 "two", //
593 "threeEEE", //
594 "four", //
595 "five", //
596 ]
597 .join("\n")
598 );
599 }
600 });
601}
602
603#[gpui::test]
604async fn test_reparse(cx: &mut gpui::TestAppContext) {
605 let text = "fn a() {}";
606 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
607
608 // Wait for the initial text to parse
609 cx.executor().run_until_parked();
610 assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
611 assert_eq!(
612 get_tree_sexp(&buffer, cx),
613 concat!(
614 "(source_file (function_item name: (identifier) ",
615 "parameters: (parameters) ",
616 "body: (block)))"
617 )
618 );
619
620 buffer.update(cx, |buffer, _| {
621 buffer.set_sync_parse_timeout(Duration::ZERO)
622 });
623
624 // Perform some edits (add parameter and variable reference)
625 // Parsing doesn't begin until the transaction is complete
626 buffer.update(cx, |buf, cx| {
627 buf.start_transaction();
628
629 let offset = buf.text().find(')').unwrap();
630 buf.edit([(offset..offset, "b: C")], None, cx);
631 assert!(!buf.is_parsing());
632
633 let offset = buf.text().find('}').unwrap();
634 buf.edit([(offset..offset, " d; ")], None, cx);
635 assert!(!buf.is_parsing());
636
637 buf.end_transaction(cx);
638 assert_eq!(buf.text(), "fn a(b: C) { d; }");
639 assert!(buf.is_parsing());
640 });
641 cx.executor().run_until_parked();
642 assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
643 assert_eq!(
644 get_tree_sexp(&buffer, cx),
645 concat!(
646 "(source_file (function_item name: (identifier) ",
647 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
648 "body: (block (expression_statement (identifier)))))"
649 )
650 );
651
652 // Perform a series of edits without waiting for the current parse to complete:
653 // * turn identifier into a field expression
654 // * turn field expression into a method call
655 // * add a turbofish to the method call
656 buffer.update(cx, |buf, cx| {
657 let offset = buf.text().find(';').unwrap();
658 buf.edit([(offset..offset, ".e")], None, cx);
659 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
660 assert!(buf.is_parsing());
661 });
662 buffer.update(cx, |buf, cx| {
663 let offset = buf.text().find(';').unwrap();
664 buf.edit([(offset..offset, "(f)")], None, cx);
665 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
666 assert!(buf.is_parsing());
667 });
668 buffer.update(cx, |buf, cx| {
669 let offset = buf.text().find("(f)").unwrap();
670 buf.edit([(offset..offset, "::<G>")], None, cx);
671 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
672 assert!(buf.is_parsing());
673 });
674 cx.executor().run_until_parked();
675 assert_eq!(
676 get_tree_sexp(&buffer, cx),
677 concat!(
678 "(source_file (function_item name: (identifier) ",
679 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
680 "body: (block (expression_statement (call_expression ",
681 "function: (generic_function ",
682 "function: (field_expression value: (identifier) field: (field_identifier)) ",
683 "type_arguments: (type_arguments (type_identifier))) ",
684 "arguments: (arguments (identifier)))))))",
685 )
686 );
687
688 buffer.update(cx, |buf, cx| {
689 buf.undo(cx);
690 buf.undo(cx);
691 buf.undo(cx);
692 buf.undo(cx);
693 assert_eq!(buf.text(), "fn a() {}");
694 assert!(buf.is_parsing());
695 });
696
697 cx.executor().run_until_parked();
698 assert_eq!(
699 get_tree_sexp(&buffer, cx),
700 concat!(
701 "(source_file (function_item name: (identifier) ",
702 "parameters: (parameters) ",
703 "body: (block)))"
704 )
705 );
706
707 buffer.update(cx, |buf, cx| {
708 buf.redo(cx);
709 buf.redo(cx);
710 buf.redo(cx);
711 buf.redo(cx);
712 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
713 assert!(buf.is_parsing());
714 });
715 cx.executor().run_until_parked();
716 assert_eq!(
717 get_tree_sexp(&buffer, cx),
718 concat!(
719 "(source_file (function_item name: (identifier) ",
720 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
721 "body: (block (expression_statement (call_expression ",
722 "function: (generic_function ",
723 "function: (field_expression value: (identifier) field: (field_identifier)) ",
724 "type_arguments: (type_arguments (type_identifier))) ",
725 "arguments: (arguments (identifier)))))))",
726 )
727 );
728}
729
730#[gpui::test]
731async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
732 let buffer = cx.new(|cx| {
733 let mut buffer = Buffer::local("{}", cx).with_language(Arc::new(rust_lang()), cx);
734 buffer.set_sync_parse_timeout(Duration::ZERO);
735 buffer
736 });
737
738 // Wait for the initial text to parse
739 cx.executor().run_until_parked();
740 assert_eq!(
741 get_tree_sexp(&buffer, cx),
742 "(source_file (expression_statement (block)))"
743 );
744
745 buffer.update(cx, |buffer, cx| {
746 buffer.set_language(Some(Arc::new(json_lang())), cx)
747 });
748 cx.executor().run_until_parked();
749 assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
750}
751
752#[gpui::test]
753async fn test_outline(cx: &mut gpui::TestAppContext) {
754 let text = r#"
755 struct Person {
756 name: String,
757 age: usize,
758 }
759
760 mod module {
761 enum LoginState {
762 LoggedOut,
763 LoggingOn,
764 LoggedIn {
765 person: Person,
766 time: Instant,
767 }
768 }
769 }
770
771 impl Eq for Person {}
772
773 impl Drop for Person {
774 fn drop(&mut self) {
775 println!("bye");
776 }
777 }
778 "#
779 .unindent();
780
781 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
782 let outline = buffer
783 .update(cx, |buffer, _| buffer.snapshot().outline(None))
784 .unwrap();
785
786 assert_eq!(
787 outline
788 .items
789 .iter()
790 .map(|item| (item.text.as_str(), item.depth))
791 .collect::<Vec<_>>(),
792 &[
793 ("struct Person", 0),
794 ("name", 1),
795 ("age", 1),
796 ("mod module", 0),
797 ("enum LoginState", 1),
798 ("LoggedOut", 2),
799 ("LoggingOn", 2),
800 ("LoggedIn", 2),
801 ("person", 3),
802 ("time", 3),
803 ("impl Eq for Person", 0),
804 ("impl Drop for Person", 0),
805 ("fn drop", 1),
806 ]
807 );
808
809 // Without space, we only match on names
810 assert_eq!(
811 search(&outline, "oon", cx).await,
812 &[
813 ("mod module", vec![]), // included as the parent of a match
814 ("enum LoginState", vec![]), // included as the parent of a match
815 ("LoggingOn", vec![1, 7, 8]), // matches
816 ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names
817 ]
818 );
819
820 assert_eq!(
821 search(&outline, "dp p", cx).await,
822 &[
823 ("impl Drop for Person", vec![5, 8, 9, 14]),
824 ("fn drop", vec![]),
825 ]
826 );
827 assert_eq!(
828 search(&outline, "dpn", cx).await,
829 &[("impl Drop for Person", vec![5, 14, 19])]
830 );
831 assert_eq!(
832 search(&outline, "impl ", cx).await,
833 &[
834 ("impl Eq for Person", vec![0, 1, 2, 3, 4]),
835 ("impl Drop for Person", vec![0, 1, 2, 3, 4]),
836 ("fn drop", vec![]),
837 ]
838 );
839
840 async fn search<'a>(
841 outline: &'a Outline<Anchor>,
842 query: &'a str,
843 cx: &'a gpui::TestAppContext,
844 ) -> Vec<(&'a str, Vec<usize>)> {
845 let matches = cx
846 .update(|cx| outline.search(query, cx.background_executor().clone()))
847 .await;
848 matches
849 .into_iter()
850 .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions))
851 .collect::<Vec<_>>()
852 }
853}
854
855#[gpui::test]
856async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
857 let text = r#"
858 impl A for B<
859 C
860 > {
861 };
862 "#
863 .unindent();
864
865 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
866 let outline = buffer
867 .update(cx, |buffer, _| buffer.snapshot().outline(None))
868 .unwrap();
869
870 assert_eq!(
871 outline
872 .items
873 .iter()
874 .map(|item| (item.text.as_str(), item.depth))
875 .collect::<Vec<_>>(),
876 &[("impl A for B<", 0)]
877 );
878}
879
880#[gpui::test]
881async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
882 let language = javascript_lang()
883 .with_outline_query(
884 r#"
885 (function_declaration
886 "function" @context
887 name: (_) @name
888 parameters: (formal_parameters
889 "(" @context.extra
890 ")" @context.extra)) @item
891 "#,
892 )
893 .unwrap();
894
895 let text = r#"
896 function a() {}
897 function b(c) {}
898 "#
899 .unindent();
900
901 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(language), cx));
902 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
903
904 // extra context nodes are included in the outline.
905 let outline = snapshot.outline(None).unwrap();
906 assert_eq!(
907 outline
908 .items
909 .iter()
910 .map(|item| (item.text.as_str(), item.depth))
911 .collect::<Vec<_>>(),
912 &[("function a()", 0), ("function b( )", 0),]
913 );
914
915 // extra context nodes do not appear in breadcrumbs.
916 let symbols = snapshot.symbols_containing(3, None).unwrap();
917 assert_eq!(
918 symbols
919 .iter()
920 .map(|item| (item.text.as_str(), item.depth))
921 .collect::<Vec<_>>(),
922 &[("function a", 0)]
923 );
924}
925
926#[gpui::test]
927fn test_outline_annotations(cx: &mut App) {
928 // Add this new test case
929 let text = r#"
930 /// This is a doc comment
931 /// that spans multiple lines
932 fn annotated_function() {
933 // This is not an annotation
934 }
935
936 // This is a single-line annotation
937 fn another_function() {}
938
939 fn unannotated_function() {}
940
941 // This comment is not an annotation
942
943 fn function_after_blank_line() {}
944 "#
945 .unindent();
946
947 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
948 let outline = buffer
949 .update(cx, |buffer, _| buffer.snapshot().outline(None))
950 .unwrap();
951
952 assert_eq!(
953 outline
954 .items
955 .into_iter()
956 .map(|item| (
957 item.text,
958 item.depth,
959 item.annotation_range
960 .map(|range| { buffer.read(cx).text_for_range(range).collect::<String>() })
961 ))
962 .collect::<Vec<_>>(),
963 &[
964 (
965 "fn annotated_function".to_string(),
966 0,
967 Some("/// This is a doc comment\n/// that spans multiple lines".to_string())
968 ),
969 (
970 "fn another_function".to_string(),
971 0,
972 Some("// This is a single-line annotation".to_string())
973 ),
974 ("fn unannotated_function".to_string(), 0, None),
975 ("fn function_after_blank_line".to_string(), 0, None),
976 ]
977 );
978}
979
980#[gpui::test]
981async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
982 let text = r#"
983 impl Person {
984 fn one() {
985 1
986 }
987
988 fn two() {
989 2
990 }fn three() {
991 3
992 }
993 }
994 "#
995 .unindent();
996
997 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
998 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
999
1000 // point is at the start of an item
1001 assert_eq!(
1002 symbols_containing(Point::new(1, 4), &snapshot),
1003 vec![
1004 (
1005 "impl Person".to_string(),
1006 Point::new(0, 0)..Point::new(10, 1)
1007 ),
1008 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
1009 ]
1010 );
1011
1012 // point is in the middle of an item
1013 assert_eq!(
1014 symbols_containing(Point::new(2, 8), &snapshot),
1015 vec![
1016 (
1017 "impl Person".to_string(),
1018 Point::new(0, 0)..Point::new(10, 1)
1019 ),
1020 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
1021 ]
1022 );
1023
1024 // point is at the end of an item
1025 assert_eq!(
1026 symbols_containing(Point::new(3, 5), &snapshot),
1027 vec![
1028 (
1029 "impl Person".to_string(),
1030 Point::new(0, 0)..Point::new(10, 1)
1031 ),
1032 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
1033 ]
1034 );
1035
1036 // point is in between two adjacent items
1037 assert_eq!(
1038 symbols_containing(Point::new(7, 5), &snapshot),
1039 vec![
1040 (
1041 "impl Person".to_string(),
1042 Point::new(0, 0)..Point::new(10, 1)
1043 ),
1044 ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5))
1045 ]
1046 );
1047
1048 fn symbols_containing(
1049 position: Point,
1050 snapshot: &BufferSnapshot,
1051 ) -> Vec<(String, Range<Point>)> {
1052 snapshot
1053 .symbols_containing(position, None)
1054 .unwrap()
1055 .into_iter()
1056 .map(|item| {
1057 (
1058 item.text,
1059 item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot),
1060 )
1061 })
1062 .collect()
1063 }
1064}
1065
1066#[gpui::test]
1067fn test_text_objects(cx: &mut App) {
1068 let (text, ranges) = marked_text_ranges(
1069 indoc! {r#"
1070 impl Hello {
1071 fn say() -> u8 { return /* ˇhi */ 1 }
1072 }"#
1073 },
1074 false,
1075 );
1076
1077 let buffer =
1078 cx.new(|cx| Buffer::local(text.clone(), cx).with_language(Arc::new(rust_lang()), cx));
1079 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
1080
1081 let matches = snapshot
1082 .text_object_ranges(ranges[0].clone(), TreeSitterOptions::default())
1083 .map(|(range, text_object)| (&text[range], text_object))
1084 .collect::<Vec<_>>();
1085
1086 assert_eq!(
1087 matches,
1088 &[
1089 ("/* hi */", TextObject::AroundComment),
1090 ("return /* hi */ 1", TextObject::InsideFunction),
1091 (
1092 "fn say() -> u8 { return /* hi */ 1 }",
1093 TextObject::AroundFunction
1094 ),
1095 ],
1096 )
1097}
1098
1099#[gpui::test]
1100fn test_enclosing_bracket_ranges(cx: &mut App) {
1101 let mut assert = |selection_text, range_markers| {
1102 assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
1103 };
1104
1105 assert(
1106 indoc! {"
1107 mod x {
1108 moˇd y {
1109
1110 }
1111 }
1112 let foo = 1;"},
1113 vec![indoc! {"
1114 mod x «{»
1115 mod y {
1116
1117 }
1118 «}»
1119 let foo = 1;"}],
1120 );
1121
1122 assert(
1123 indoc! {"
1124 mod x {
1125 mod y ˇ{
1126
1127 }
1128 }
1129 let foo = 1;"},
1130 vec![
1131 indoc! {"
1132 mod x «{»
1133 mod y {
1134
1135 }
1136 «}»
1137 let foo = 1;"},
1138 indoc! {"
1139 mod x {
1140 mod y «{»
1141
1142 «}»
1143 }
1144 let foo = 1;"},
1145 ],
1146 );
1147
1148 assert(
1149 indoc! {"
1150 mod x {
1151 mod y {
1152
1153 }ˇ
1154 }
1155 let foo = 1;"},
1156 vec![
1157 indoc! {"
1158 mod x «{»
1159 mod y {
1160
1161 }
1162 «}»
1163 let foo = 1;"},
1164 indoc! {"
1165 mod x {
1166 mod y «{»
1167
1168 «}»
1169 }
1170 let foo = 1;"},
1171 ],
1172 );
1173
1174 assert(
1175 indoc! {"
1176 mod x {
1177 mod y {
1178
1179 }
1180 ˇ}
1181 let foo = 1;"},
1182 vec![indoc! {"
1183 mod x «{»
1184 mod y {
1185
1186 }
1187 «}»
1188 let foo = 1;"}],
1189 );
1190
1191 assert(
1192 indoc! {"
1193 mod x {
1194 mod y {
1195
1196 }
1197 }
1198 let fˇoo = 1;"},
1199 vec![],
1200 );
1201
1202 // Regression test: avoid crash when querying at the end of the buffer.
1203 assert(
1204 indoc! {"
1205 mod x {
1206 mod y {
1207
1208 }
1209 }
1210 let foo = 1;ˇ"},
1211 vec![],
1212 );
1213}
1214
1215#[gpui::test]
1216fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut App) {
1217 let mut assert = |selection_text, bracket_pair_texts| {
1218 assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
1219 };
1220
1221 assert(
1222 indoc! {"
1223 for (const a in b)ˇ {
1224 // a comment that's longer than the for-loop header
1225 }"},
1226 vec![indoc! {"
1227 for «(»const a in b«)» {
1228 // a comment that's longer than the for-loop header
1229 }"}],
1230 );
1231
1232 // Regression test: even though the parent node of the parentheses (the for loop) does
1233 // intersect the given range, the parentheses themselves do not contain the range, so
1234 // they should not be returned. Only the curly braces contain the range.
1235 assert(
1236 indoc! {"
1237 for (const a in b) {ˇ
1238 // a comment that's longer than the for-loop header
1239 }"},
1240 vec![indoc! {"
1241 for (const a in b) «{»
1242 // a comment that's longer than the for-loop header
1243 «}»"}],
1244 );
1245}
1246
1247#[gpui::test]
1248fn test_range_for_syntax_ancestor(cx: &mut App) {
1249 cx.new(|cx| {
1250 let text = "fn a() { b(|c| {}) }";
1251 let buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1252 let snapshot = buffer.snapshot();
1253
1254 assert_eq!(
1255 snapshot
1256 .syntax_ancestor(empty_range_at(text, "|"))
1257 .unwrap()
1258 .byte_range(),
1259 range_of(text, "|")
1260 );
1261 assert_eq!(
1262 snapshot
1263 .syntax_ancestor(range_of(text, "|"))
1264 .unwrap()
1265 .byte_range(),
1266 range_of(text, "|c|")
1267 );
1268 assert_eq!(
1269 snapshot
1270 .syntax_ancestor(range_of(text, "|c|"))
1271 .unwrap()
1272 .byte_range(),
1273 range_of(text, "|c| {}")
1274 );
1275 assert_eq!(
1276 snapshot
1277 .syntax_ancestor(range_of(text, "|c| {}"))
1278 .unwrap()
1279 .byte_range(),
1280 range_of(text, "(|c| {})")
1281 );
1282
1283 buffer
1284 });
1285
1286 fn empty_range_at(text: &str, part: &str) -> Range<usize> {
1287 let start = text.find(part).unwrap();
1288 start..start
1289 }
1290
1291 fn range_of(text: &str, part: &str) -> Range<usize> {
1292 let start = text.find(part).unwrap();
1293 start..start + part.len()
1294 }
1295}
1296
1297#[gpui::test]
1298fn test_autoindent_with_soft_tabs(cx: &mut App) {
1299 init_settings(cx, |_| {});
1300
1301 cx.new(|cx| {
1302 let text = "fn a() {}";
1303 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1304
1305 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1306 assert_eq!(buffer.text(), "fn a() {\n \n}");
1307
1308 buffer.edit(
1309 [(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
1310 Some(AutoindentMode::EachLine),
1311 cx,
1312 );
1313 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
1314
1315 // Create a field expression on a new line, causing that line
1316 // to be indented.
1317 buffer.edit(
1318 [(Point::new(2, 4)..Point::new(2, 4), ".c")],
1319 Some(AutoindentMode::EachLine),
1320 cx,
1321 );
1322 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
1323
1324 // Remove the dot so that the line is no longer a field expression,
1325 // causing the line to be outdented.
1326 buffer.edit(
1327 [(Point::new(2, 8)..Point::new(2, 9), "")],
1328 Some(AutoindentMode::EachLine),
1329 cx,
1330 );
1331 assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}");
1332
1333 buffer
1334 });
1335}
1336
1337#[gpui::test]
1338fn test_autoindent_with_hard_tabs(cx: &mut App) {
1339 init_settings(cx, |settings| {
1340 settings.defaults.hard_tabs = Some(true);
1341 });
1342
1343 cx.new(|cx| {
1344 let text = "fn a() {}";
1345 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1346
1347 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1348 assert_eq!(buffer.text(), "fn a() {\n\t\n}");
1349
1350 buffer.edit(
1351 [(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
1352 Some(AutoindentMode::EachLine),
1353 cx,
1354 );
1355 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
1356
1357 // Create a field expression on a new line, causing that line
1358 // to be indented.
1359 buffer.edit(
1360 [(Point::new(2, 1)..Point::new(2, 1), ".c")],
1361 Some(AutoindentMode::EachLine),
1362 cx,
1363 );
1364 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
1365
1366 // Remove the dot so that the line is no longer a field expression,
1367 // causing the line to be outdented.
1368 buffer.edit(
1369 [(Point::new(2, 2)..Point::new(2, 3), "")],
1370 Some(AutoindentMode::EachLine),
1371 cx,
1372 );
1373 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
1374
1375 buffer
1376 });
1377}
1378
1379#[gpui::test]
1380fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut App) {
1381 init_settings(cx, |_| {});
1382
1383 cx.new(|cx| {
1384 let mut buffer = Buffer::local(
1385 "
1386 fn a() {
1387 c;
1388 d;
1389 }
1390 "
1391 .unindent(),
1392 cx,
1393 )
1394 .with_language(Arc::new(rust_lang()), cx);
1395
1396 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
1397 // their indentation is not adjusted.
1398 buffer.edit_via_marked_text(
1399 &"
1400 fn a() {
1401 c«()»;
1402 d«()»;
1403 }
1404 "
1405 .unindent(),
1406 Some(AutoindentMode::EachLine),
1407 cx,
1408 );
1409 assert_eq!(
1410 buffer.text(),
1411 "
1412 fn a() {
1413 c();
1414 d();
1415 }
1416 "
1417 .unindent()
1418 );
1419
1420 // When appending new content after these lines, the indentation is based on the
1421 // preceding lines' actual indentation.
1422 buffer.edit_via_marked_text(
1423 &"
1424 fn a() {
1425 c«
1426 .f
1427 .g()»;
1428 d«
1429 .f
1430 .g()»;
1431 }
1432 "
1433 .unindent(),
1434 Some(AutoindentMode::EachLine),
1435 cx,
1436 );
1437 assert_eq!(
1438 buffer.text(),
1439 "
1440 fn a() {
1441 c
1442 .f
1443 .g();
1444 d
1445 .f
1446 .g();
1447 }
1448 "
1449 .unindent()
1450 );
1451
1452 // Insert a newline after the open brace. It is auto-indented
1453 buffer.edit_via_marked_text(
1454 &"
1455 fn a() {«
1456 »
1457 c
1458 .f
1459 .g();
1460 d
1461 .f
1462 .g();
1463 }
1464 "
1465 .unindent(),
1466 Some(AutoindentMode::EachLine),
1467 cx,
1468 );
1469 assert_eq!(
1470 buffer.text(),
1471 "
1472 fn a() {
1473 ˇ
1474 c
1475 .f
1476 .g();
1477 d
1478 .f
1479 .g();
1480 }
1481 "
1482 .unindent()
1483 .replace("ˇ", "")
1484 );
1485
1486 // Manually outdent the line. It stays outdented.
1487 buffer.edit_via_marked_text(
1488 &"
1489 fn a() {
1490 «»
1491 c
1492 .f
1493 .g();
1494 d
1495 .f
1496 .g();
1497 }
1498 "
1499 .unindent(),
1500 Some(AutoindentMode::EachLine),
1501 cx,
1502 );
1503 assert_eq!(
1504 buffer.text(),
1505 "
1506 fn a() {
1507
1508 c
1509 .f
1510 .g();
1511 d
1512 .f
1513 .g();
1514 }
1515 "
1516 .unindent()
1517 );
1518
1519 buffer
1520 });
1521
1522 cx.new(|cx| {
1523 eprintln!("second buffer: {:?}", cx.entity_id());
1524
1525 let mut buffer = Buffer::local(
1526 "
1527 fn a() {
1528 b();
1529 |
1530 "
1531 .replace('|', "") // marker to preserve trailing whitespace
1532 .unindent(),
1533 cx,
1534 )
1535 .with_language(Arc::new(rust_lang()), cx);
1536
1537 // Insert a closing brace. It is outdented.
1538 buffer.edit_via_marked_text(
1539 &"
1540 fn a() {
1541 b();
1542 «}»
1543 "
1544 .unindent(),
1545 Some(AutoindentMode::EachLine),
1546 cx,
1547 );
1548 assert_eq!(
1549 buffer.text(),
1550 "
1551 fn a() {
1552 b();
1553 }
1554 "
1555 .unindent()
1556 );
1557
1558 // Manually edit the leading whitespace. The edit is preserved.
1559 buffer.edit_via_marked_text(
1560 &"
1561 fn a() {
1562 b();
1563 « »}
1564 "
1565 .unindent(),
1566 Some(AutoindentMode::EachLine),
1567 cx,
1568 );
1569 assert_eq!(
1570 buffer.text(),
1571 "
1572 fn a() {
1573 b();
1574 }
1575 "
1576 .unindent()
1577 );
1578 buffer
1579 });
1580
1581 eprintln!("DONE");
1582}
1583
1584#[gpui::test]
1585fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut App) {
1586 init_settings(cx, |_| {});
1587
1588 cx.new(|cx| {
1589 let mut buffer = Buffer::local(
1590 "
1591 fn a() {
1592 i
1593 }
1594 "
1595 .unindent(),
1596 cx,
1597 )
1598 .with_language(Arc::new(rust_lang()), cx);
1599
1600 // Regression test: line does not get outdented due to syntax error
1601 buffer.edit_via_marked_text(
1602 &"
1603 fn a() {
1604 i«f let Some(x) = y»
1605 }
1606 "
1607 .unindent(),
1608 Some(AutoindentMode::EachLine),
1609 cx,
1610 );
1611 assert_eq!(
1612 buffer.text(),
1613 "
1614 fn a() {
1615 if let Some(x) = y
1616 }
1617 "
1618 .unindent()
1619 );
1620
1621 buffer.edit_via_marked_text(
1622 &"
1623 fn a() {
1624 if let Some(x) = y« {»
1625 }
1626 "
1627 .unindent(),
1628 Some(AutoindentMode::EachLine),
1629 cx,
1630 );
1631 assert_eq!(
1632 buffer.text(),
1633 "
1634 fn a() {
1635 if let Some(x) = y {
1636 }
1637 "
1638 .unindent()
1639 );
1640
1641 buffer
1642 });
1643}
1644
1645#[gpui::test]
1646fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut App) {
1647 init_settings(cx, |_| {});
1648
1649 cx.new(|cx| {
1650 let mut buffer = Buffer::local(
1651 "
1652 fn a() {}
1653 "
1654 .unindent(),
1655 cx,
1656 )
1657 .with_language(Arc::new(rust_lang()), cx);
1658
1659 buffer.edit_via_marked_text(
1660 &"
1661 fn a(«
1662 b») {}
1663 "
1664 .unindent(),
1665 Some(AutoindentMode::EachLine),
1666 cx,
1667 );
1668 assert_eq!(
1669 buffer.text(),
1670 "
1671 fn a(
1672 b) {}
1673 "
1674 .unindent()
1675 );
1676
1677 // The indentation suggestion changed because `@end` node (a close paren)
1678 // is now at the beginning of the line.
1679 buffer.edit_via_marked_text(
1680 &"
1681 fn a(
1682 ˇ) {}
1683 "
1684 .unindent(),
1685 Some(AutoindentMode::EachLine),
1686 cx,
1687 );
1688 assert_eq!(
1689 buffer.text(),
1690 "
1691 fn a(
1692 ) {}
1693 "
1694 .unindent()
1695 );
1696
1697 buffer
1698 });
1699}
1700
1701#[gpui::test]
1702fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut App) {
1703 init_settings(cx, |_| {});
1704
1705 cx.new(|cx| {
1706 let text = "a\nb";
1707 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1708 buffer.edit(
1709 [(0..1, "\n"), (2..3, "\n")],
1710 Some(AutoindentMode::EachLine),
1711 cx,
1712 );
1713 assert_eq!(buffer.text(), "\n\n\n");
1714 buffer
1715 });
1716}
1717
1718#[gpui::test]
1719fn test_autoindent_multi_line_insertion(cx: &mut App) {
1720 init_settings(cx, |_| {});
1721
1722 cx.new(|cx| {
1723 let text = "
1724 const a: usize = 1;
1725 fn b() {
1726 if c {
1727 let d = 2;
1728 }
1729 }
1730 "
1731 .unindent();
1732
1733 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1734 buffer.edit(
1735 [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
1736 Some(AutoindentMode::EachLine),
1737 cx,
1738 );
1739 assert_eq!(
1740 buffer.text(),
1741 "
1742 const a: usize = 1;
1743 fn b() {
1744 if c {
1745 e(
1746 f()
1747 );
1748 let d = 2;
1749 }
1750 }
1751 "
1752 .unindent()
1753 );
1754
1755 buffer
1756 });
1757}
1758
1759#[gpui::test]
1760fn test_autoindent_block_mode(cx: &mut App) {
1761 init_settings(cx, |_| {});
1762
1763 cx.new(|cx| {
1764 let text = r#"
1765 fn a() {
1766 b();
1767 }
1768 "#
1769 .unindent();
1770 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1771
1772 // When this text was copied, both of the quotation marks were at the same
1773 // indent level, but the indentation of the first line was not included in
1774 // the copied text. This information is retained in the
1775 // 'original_indent_columns' vector.
1776 let original_indent_columns = vec![Some(4)];
1777 let inserted_text = r#"
1778 "
1779 c
1780 d
1781 e
1782 "
1783 "#
1784 .unindent();
1785
1786 // Insert the block at column zero. The entire block is indented
1787 // so that the first line matches the previous line's indentation.
1788 buffer.edit(
1789 [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
1790 Some(AutoindentMode::Block {
1791 original_indent_columns: original_indent_columns.clone(),
1792 }),
1793 cx,
1794 );
1795 assert_eq!(
1796 buffer.text(),
1797 r#"
1798 fn a() {
1799 b();
1800 "
1801 c
1802 d
1803 e
1804 "
1805 }
1806 "#
1807 .unindent()
1808 );
1809
1810 // Grouping is disabled in tests, so we need 2 undos
1811 buffer.undo(cx); // Undo the auto-indent
1812 buffer.undo(cx); // Undo the original edit
1813
1814 // Insert the block at a deeper indent level. The entire block is outdented.
1815 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
1816 buffer.edit(
1817 [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
1818 Some(AutoindentMode::Block {
1819 original_indent_columns,
1820 }),
1821 cx,
1822 );
1823 assert_eq!(
1824 buffer.text(),
1825 r#"
1826 fn a() {
1827 b();
1828 "
1829 c
1830 d
1831 e
1832 "
1833 }
1834 "#
1835 .unindent()
1836 );
1837
1838 buffer
1839 });
1840}
1841
1842#[gpui::test]
1843fn test_autoindent_block_mode_with_newline(cx: &mut App) {
1844 init_settings(cx, |_| {});
1845
1846 cx.new(|cx| {
1847 let text = r#"
1848 fn a() {
1849 b();
1850 }
1851 "#
1852 .unindent();
1853 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1854
1855 // First line contains just '\n', it's indentation is stored in "original_indent_columns"
1856 let original_indent_columns = vec![Some(4)];
1857 let inserted_text = r#"
1858
1859 c();
1860 d();
1861 e();
1862 "#
1863 .unindent();
1864 buffer.edit(
1865 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
1866 Some(AutoindentMode::Block {
1867 original_indent_columns,
1868 }),
1869 cx,
1870 );
1871
1872 // While making edit, we ignore first line as it only contains '\n'
1873 // hence second line indent is used to calculate delta
1874 assert_eq!(
1875 buffer.text(),
1876 r#"
1877 fn a() {
1878 b();
1879
1880 c();
1881 d();
1882 e();
1883 }
1884 "#
1885 .unindent()
1886 );
1887
1888 buffer
1889 });
1890}
1891
1892#[gpui::test]
1893fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut App) {
1894 init_settings(cx, |_| {});
1895
1896 cx.new(|cx| {
1897 let text = r#"
1898 fn a() {
1899 if b() {
1900
1901 }
1902 }
1903 "#
1904 .unindent();
1905 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1906
1907 // The original indent columns are not known, so this text is
1908 // auto-indented in a block as if the first line was copied in
1909 // its entirety.
1910 let original_indent_columns = Vec::new();
1911 let inserted_text = " c\n .d()\n .e();";
1912
1913 // Insert the block at column zero. The entire block is indented
1914 // so that the first line matches the previous line's indentation.
1915 buffer.edit(
1916 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
1917 Some(AutoindentMode::Block {
1918 original_indent_columns,
1919 }),
1920 cx,
1921 );
1922 assert_eq!(
1923 buffer.text(),
1924 r#"
1925 fn a() {
1926 if b() {
1927 c
1928 .d()
1929 .e();
1930 }
1931 }
1932 "#
1933 .unindent()
1934 );
1935
1936 // Grouping is disabled in tests, so we need 2 undos
1937 buffer.undo(cx); // Undo the auto-indent
1938 buffer.undo(cx); // Undo the original edit
1939
1940 // Insert the block at a deeper indent level. The entire block is outdented.
1941 buffer.edit(
1942 [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
1943 None,
1944 cx,
1945 );
1946 buffer.edit(
1947 [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
1948 Some(AutoindentMode::Block {
1949 original_indent_columns: Vec::new(),
1950 }),
1951 cx,
1952 );
1953 assert_eq!(
1954 buffer.text(),
1955 r#"
1956 fn a() {
1957 if b() {
1958 c
1959 .d()
1960 .e();
1961 }
1962 }
1963 "#
1964 .unindent()
1965 );
1966
1967 buffer
1968 });
1969}
1970
1971#[gpui::test]
1972fn test_autoindent_block_mode_multiple_adjacent_ranges(cx: &mut App) {
1973 init_settings(cx, |_| {});
1974
1975 cx.new(|cx| {
1976 let (text, ranges_to_replace) = marked_text_ranges(
1977 &"
1978 mod numbers {
1979 «fn one() {
1980 1
1981 }
1982 »
1983 «fn two() {
1984 2
1985 }
1986 »
1987 «fn three() {
1988 3
1989 }
1990 »}
1991 "
1992 .unindent(),
1993 false,
1994 );
1995
1996 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1997
1998 buffer.edit(
1999 [
2000 (ranges_to_replace[0].clone(), "fn one() {\n 101\n}\n"),
2001 (ranges_to_replace[1].clone(), "fn two() {\n 102\n}\n"),
2002 (ranges_to_replace[2].clone(), "fn three() {\n 103\n}\n"),
2003 ],
2004 Some(AutoindentMode::Block {
2005 original_indent_columns: vec![Some(0), Some(0), Some(0)],
2006 }),
2007 cx,
2008 );
2009
2010 pretty_assertions::assert_eq!(
2011 buffer.text(),
2012 "
2013 mod numbers {
2014 fn one() {
2015 101
2016 }
2017
2018 fn two() {
2019 102
2020 }
2021
2022 fn three() {
2023 103
2024 }
2025 }
2026 "
2027 .unindent()
2028 );
2029
2030 buffer
2031 });
2032}
2033
2034#[gpui::test]
2035fn test_autoindent_language_without_indents_query(cx: &mut App) {
2036 init_settings(cx, |_| {});
2037
2038 cx.new(|cx| {
2039 let text = "
2040 * one
2041 - a
2042 - b
2043 * two
2044 "
2045 .unindent();
2046
2047 let mut buffer = Buffer::local(text, cx).with_language(
2048 Arc::new(Language::new(
2049 LanguageConfig {
2050 name: "Markdown".into(),
2051 auto_indent_using_last_non_empty_line: false,
2052 ..Default::default()
2053 },
2054 Some(tree_sitter_json::LANGUAGE.into()),
2055 )),
2056 cx,
2057 );
2058 buffer.edit(
2059 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
2060 Some(AutoindentMode::EachLine),
2061 cx,
2062 );
2063 assert_eq!(
2064 buffer.text(),
2065 "
2066 * one
2067 - a
2068 - b
2069
2070 * two
2071 "
2072 .unindent()
2073 );
2074 buffer
2075 });
2076}
2077
2078#[gpui::test]
2079fn test_autoindent_with_injected_languages(cx: &mut App) {
2080 init_settings(cx, |settings| {
2081 settings.languages.0.extend([
2082 (
2083 "HTML".into(),
2084 LanguageSettingsContent {
2085 tab_size: Some(2.try_into().unwrap()),
2086 ..Default::default()
2087 },
2088 ),
2089 (
2090 "JavaScript".into(),
2091 LanguageSettingsContent {
2092 tab_size: Some(8.try_into().unwrap()),
2093 ..Default::default()
2094 },
2095 ),
2096 ])
2097 });
2098
2099 let html_language = Arc::new(html_lang());
2100
2101 let javascript_language = Arc::new(javascript_lang());
2102
2103 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2104 language_registry.add(html_language.clone());
2105 language_registry.add(javascript_language);
2106
2107 cx.new(|cx| {
2108 let (text, ranges) = marked_text_ranges(
2109 &"
2110 <div>ˇ
2111 </div>
2112 <script>
2113 init({ˇ
2114 })
2115 </script>
2116 <span>ˇ
2117 </span>
2118 "
2119 .unindent(),
2120 false,
2121 );
2122
2123 let mut buffer = Buffer::local(text, cx);
2124 buffer.set_language_registry(language_registry);
2125 buffer.set_language(Some(html_language), cx);
2126 buffer.edit(
2127 ranges.into_iter().map(|range| (range, "\na")),
2128 Some(AutoindentMode::EachLine),
2129 cx,
2130 );
2131 assert_eq!(
2132 buffer.text(),
2133 "
2134 <div>
2135 a
2136 </div>
2137 <script>
2138 init({
2139 a
2140 })
2141 </script>
2142 <span>
2143 a
2144 </span>
2145 "
2146 .unindent()
2147 );
2148 buffer
2149 });
2150}
2151
2152#[gpui::test]
2153fn test_autoindent_query_with_outdent_captures(cx: &mut App) {
2154 init_settings(cx, |settings| {
2155 settings.defaults.tab_size = Some(2.try_into().unwrap());
2156 });
2157
2158 cx.new(|cx| {
2159 let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
2160
2161 let text = r#"
2162 class C
2163 def a(b, c)
2164 puts b
2165 puts c
2166 rescue
2167 puts "errored"
2168 exit 1
2169 end
2170 end
2171 "#
2172 .unindent();
2173
2174 buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
2175
2176 assert_eq!(
2177 buffer.text(),
2178 r#"
2179 class C
2180 def a(b, c)
2181 puts b
2182 puts c
2183 rescue
2184 puts "errored"
2185 exit 1
2186 end
2187 end
2188 "#
2189 .unindent()
2190 );
2191
2192 buffer
2193 });
2194}
2195
2196#[gpui::test]
2197async fn test_async_autoindents_preserve_preview(cx: &mut TestAppContext) {
2198 cx.update(|cx| init_settings(cx, |_| {}));
2199
2200 // First we insert some newlines to request an auto-indent (asynchronously).
2201 // Then we request that a preview tab be preserved for the new version, even though it's edited.
2202 let buffer = cx.new(|cx| {
2203 let text = "fn a() {}";
2204 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
2205
2206 // This causes autoindent to be async.
2207 buffer.set_sync_parse_timeout(Duration::ZERO);
2208
2209 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
2210 buffer.refresh_preview();
2211
2212 // Synchronously, we haven't auto-indented and we're still preserving the preview.
2213 assert_eq!(buffer.text(), "fn a() {\n\n}");
2214 assert!(buffer.preserve_preview());
2215 buffer
2216 });
2217
2218 // Now let the autoindent finish
2219 cx.executor().run_until_parked();
2220
2221 // The auto-indent applied, but didn't dismiss our preview
2222 buffer.update(cx, |buffer, cx| {
2223 assert_eq!(buffer.text(), "fn a() {\n \n}");
2224 assert!(buffer.preserve_preview());
2225
2226 // Edit inserting another line. It will autoindent async.
2227 // Then refresh the preview version.
2228 buffer.edit(
2229 [(Point::new(1, 4)..Point::new(1, 4), "\n")],
2230 Some(AutoindentMode::EachLine),
2231 cx,
2232 );
2233 buffer.refresh_preview();
2234 assert_eq!(buffer.text(), "fn a() {\n \n\n}");
2235 assert!(buffer.preserve_preview());
2236
2237 // Then perform another edit, this time without refreshing the preview version.
2238 buffer.edit([(Point::new(1, 4)..Point::new(1, 4), "x")], None, cx);
2239 // This causes the preview to not be preserved.
2240 assert!(!buffer.preserve_preview());
2241 });
2242
2243 // Let the async autoindent from the first edit finish.
2244 cx.executor().run_until_parked();
2245
2246 // The autoindent applies, but it shouldn't restore the preview status because we had an edit in the meantime.
2247 buffer.update(cx, |buffer, _| {
2248 assert_eq!(buffer.text(), "fn a() {\n x\n \n}");
2249 assert!(!buffer.preserve_preview());
2250 });
2251}
2252
2253#[gpui::test]
2254fn test_insert_empty_line(cx: &mut App) {
2255 init_settings(cx, |_| {});
2256
2257 // Insert empty line at the beginning, requesting an empty line above
2258 cx.new(|cx| {
2259 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2260 let point = buffer.insert_empty_line(Point::new(0, 0), true, false, cx);
2261 assert_eq!(buffer.text(), "\nabc\ndef\nghi");
2262 assert_eq!(point, Point::new(0, 0));
2263 buffer
2264 });
2265
2266 // Insert empty line at the beginning, requesting an empty line above and below
2267 cx.new(|cx| {
2268 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2269 let point = buffer.insert_empty_line(Point::new(0, 0), true, true, cx);
2270 assert_eq!(buffer.text(), "\n\nabc\ndef\nghi");
2271 assert_eq!(point, Point::new(0, 0));
2272 buffer
2273 });
2274
2275 // Insert empty line at the start of a line, requesting empty lines above and below
2276 cx.new(|cx| {
2277 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2278 let point = buffer.insert_empty_line(Point::new(2, 0), true, true, cx);
2279 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi");
2280 assert_eq!(point, Point::new(3, 0));
2281 buffer
2282 });
2283
2284 // Insert empty line in the middle of a line, requesting empty lines above and below
2285 cx.new(|cx| {
2286 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2287 let point = buffer.insert_empty_line(Point::new(1, 3), true, true, cx);
2288 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi\njkl");
2289 assert_eq!(point, Point::new(3, 0));
2290 buffer
2291 });
2292
2293 // Insert empty line in the middle of a line, requesting empty line above only
2294 cx.new(|cx| {
2295 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2296 let point = buffer.insert_empty_line(Point::new(1, 3), true, false, cx);
2297 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2298 assert_eq!(point, Point::new(3, 0));
2299 buffer
2300 });
2301
2302 // Insert empty line in the middle of a line, requesting empty line below only
2303 cx.new(|cx| {
2304 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2305 let point = buffer.insert_empty_line(Point::new(1, 3), false, true, cx);
2306 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2307 assert_eq!(point, Point::new(2, 0));
2308 buffer
2309 });
2310
2311 // Insert empty line at the end, requesting empty lines above and below
2312 cx.new(|cx| {
2313 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2314 let point = buffer.insert_empty_line(Point::new(2, 3), true, true, cx);
2315 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n\n");
2316 assert_eq!(point, Point::new(4, 0));
2317 buffer
2318 });
2319
2320 // Insert empty line at the end, requesting empty line above only
2321 cx.new(|cx| {
2322 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2323 let point = buffer.insert_empty_line(Point::new(2, 3), true, false, cx);
2324 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2325 assert_eq!(point, Point::new(4, 0));
2326 buffer
2327 });
2328
2329 // Insert empty line at the end, requesting empty line below only
2330 cx.new(|cx| {
2331 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2332 let point = buffer.insert_empty_line(Point::new(2, 3), false, true, cx);
2333 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2334 assert_eq!(point, Point::new(3, 0));
2335 buffer
2336 });
2337}
2338
2339#[gpui::test]
2340fn test_language_scope_at_with_javascript(cx: &mut App) {
2341 init_settings(cx, |_| {});
2342
2343 cx.new(|cx| {
2344 let language = Language::new(
2345 LanguageConfig {
2346 name: "JavaScript".into(),
2347 line_comments: vec!["// ".into()],
2348 block_comment: Some(BlockCommentConfig {
2349 start: "/*".into(),
2350 end: "*/".into(),
2351 prefix: "* ".into(),
2352 tab_size: 1,
2353 }),
2354 brackets: BracketPairConfig {
2355 pairs: vec![
2356 BracketPair {
2357 start: "{".into(),
2358 end: "}".into(),
2359 close: true,
2360 surround: true,
2361 newline: false,
2362 },
2363 BracketPair {
2364 start: "'".into(),
2365 end: "'".into(),
2366 close: true,
2367 surround: true,
2368 newline: false,
2369 },
2370 ],
2371 disabled_scopes_by_bracket_ix: vec![
2372 Vec::new(), //
2373 vec!["string".into(), "comment".into()], // single quotes disabled
2374 ],
2375 },
2376 overrides: [(
2377 "element".into(),
2378 LanguageConfigOverride {
2379 line_comments: Override::Remove { remove: true },
2380 block_comment: Override::Set(BlockCommentConfig {
2381 start: "{/*".into(),
2382 prefix: "".into(),
2383 end: "*/}".into(),
2384 tab_size: 0,
2385 }),
2386 ..Default::default()
2387 },
2388 )]
2389 .into_iter()
2390 .collect(),
2391 ..Default::default()
2392 },
2393 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
2394 )
2395 .with_override_query(
2396 r#"
2397 (jsx_element) @element
2398 (string) @string
2399 (comment) @comment.inclusive
2400 [
2401 (jsx_opening_element)
2402 (jsx_closing_element)
2403 (jsx_expression)
2404 ] @default
2405 "#,
2406 )
2407 .unwrap();
2408
2409 let text = r#"
2410 a["b"] = <C d="e">
2411 <F></F>
2412 { g() }
2413 </C>; // a comment
2414 "#
2415 .unindent();
2416
2417 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
2418 let snapshot = buffer.snapshot();
2419
2420 let config = snapshot.language_scope_at(0).unwrap();
2421 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
2422 assert_eq!(
2423 config.block_comment(),
2424 Some(&BlockCommentConfig {
2425 start: "/*".into(),
2426 prefix: "* ".into(),
2427 end: "*/".into(),
2428 tab_size: 1,
2429 })
2430 );
2431
2432 // Both bracket pairs are enabled
2433 assert_eq!(
2434 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2435 &[true, true]
2436 );
2437
2438 let comment_config = snapshot
2439 .language_scope_at(text.find("comment").unwrap() + "comment".len())
2440 .unwrap();
2441 assert_eq!(
2442 comment_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2443 &[true, false]
2444 );
2445
2446 let string_config = snapshot
2447 .language_scope_at(text.find("b\"").unwrap())
2448 .unwrap();
2449 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
2450 assert_eq!(
2451 string_config.block_comment(),
2452 Some(&BlockCommentConfig {
2453 start: "/*".into(),
2454 prefix: "* ".into(),
2455 end: "*/".into(),
2456 tab_size: 1,
2457 })
2458 );
2459 // Second bracket pair is disabled
2460 assert_eq!(
2461 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2462 &[true, false]
2463 );
2464
2465 // In between JSX tags: use the `element` override.
2466 let element_config = snapshot
2467 .language_scope_at(text.find("<F>").unwrap())
2468 .unwrap();
2469 // TODO nested blocks after newlines are captured with all whitespaces
2470 // https://github.com/tree-sitter/tree-sitter-typescript/issues/306
2471 // assert_eq!(element_config.line_comment_prefixes(), &[]);
2472 // assert_eq!(
2473 // element_config.block_comment_delimiters(),
2474 // Some((&"{/*".into(), &"*/}".into()))
2475 // );
2476 assert_eq!(
2477 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2478 &[true, true]
2479 );
2480
2481 // Within a JSX tag: use the default config.
2482 let tag_config = snapshot
2483 .language_scope_at(text.find(" d=").unwrap() + 1)
2484 .unwrap();
2485 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
2486 assert_eq!(
2487 tag_config.block_comment(),
2488 Some(&BlockCommentConfig {
2489 start: "/*".into(),
2490 prefix: "* ".into(),
2491 end: "*/".into(),
2492 tab_size: 1,
2493 })
2494 );
2495 assert_eq!(
2496 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2497 &[true, true]
2498 );
2499
2500 // In a JSX expression: use the default config.
2501 let expression_in_element_config = snapshot
2502 .language_scope_at(text.find('{').unwrap() + 1)
2503 .unwrap();
2504 assert_eq!(
2505 expression_in_element_config.line_comment_prefixes(),
2506 &[Arc::from("// ")]
2507 );
2508 assert_eq!(
2509 expression_in_element_config.block_comment(),
2510 Some(&BlockCommentConfig {
2511 start: "/*".into(),
2512 prefix: "* ".into(),
2513 end: "*/".into(),
2514 tab_size: 1,
2515 })
2516 );
2517 assert_eq!(
2518 expression_in_element_config
2519 .brackets()
2520 .map(|e| e.1)
2521 .collect::<Vec<_>>(),
2522 &[true, true]
2523 );
2524
2525 buffer
2526 });
2527}
2528
2529#[gpui::test]
2530fn test_language_scope_at_with_rust(cx: &mut App) {
2531 init_settings(cx, |_| {});
2532
2533 cx.new(|cx| {
2534 let language = Language::new(
2535 LanguageConfig {
2536 name: "Rust".into(),
2537 brackets: BracketPairConfig {
2538 pairs: vec![
2539 BracketPair {
2540 start: "{".into(),
2541 end: "}".into(),
2542 close: true,
2543 surround: true,
2544 newline: false,
2545 },
2546 BracketPair {
2547 start: "'".into(),
2548 end: "'".into(),
2549 close: true,
2550 surround: true,
2551 newline: false,
2552 },
2553 ],
2554 disabled_scopes_by_bracket_ix: vec![
2555 Vec::new(), //
2556 vec!["string".into()],
2557 ],
2558 },
2559 ..Default::default()
2560 },
2561 Some(tree_sitter_rust::LANGUAGE.into()),
2562 )
2563 .with_override_query(
2564 r#"
2565 (string_literal) @string
2566 "#,
2567 )
2568 .unwrap();
2569
2570 let text = r#"
2571 const S: &'static str = "hello";
2572 "#
2573 .unindent();
2574
2575 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
2576 let snapshot = buffer.snapshot();
2577
2578 // By default, all brackets are enabled
2579 let config = snapshot.language_scope_at(0).unwrap();
2580 assert_eq!(
2581 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2582 &[true, true]
2583 );
2584
2585 // Within a string, the quotation brackets are disabled.
2586 let string_config = snapshot
2587 .language_scope_at(text.find("ello").unwrap())
2588 .unwrap();
2589 assert_eq!(
2590 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2591 &[true, false]
2592 );
2593
2594 buffer
2595 });
2596}
2597
2598#[gpui::test]
2599fn test_language_scope_at_with_combined_injections(cx: &mut App) {
2600 init_settings(cx, |_| {});
2601
2602 cx.new(|cx| {
2603 let text = r#"
2604 <ol>
2605 <% people.each do |person| %>
2606 <li>
2607 <%= person.name %>
2608 </li>
2609 <% end %>
2610 </ol>
2611 "#
2612 .unindent();
2613
2614 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2615 language_registry.add(Arc::new(ruby_lang()));
2616 language_registry.add(Arc::new(html_lang()));
2617 language_registry.add(Arc::new(erb_lang()));
2618
2619 let mut buffer = Buffer::local(text, cx);
2620 buffer.set_language_registry(language_registry.clone());
2621 buffer.set_language(
2622 language_registry
2623 .language_for_name("ERB")
2624 .now_or_never()
2625 .unwrap()
2626 .ok(),
2627 cx,
2628 );
2629
2630 let snapshot = buffer.snapshot();
2631 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2632 assert_eq!(html_config.line_comment_prefixes(), &[]);
2633 assert_eq!(
2634 html_config.block_comment(),
2635 Some(&BlockCommentConfig {
2636 start: "<!--".into(),
2637 end: "-->".into(),
2638 prefix: "".into(),
2639 tab_size: 0,
2640 })
2641 );
2642
2643 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2644 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2645 assert_eq!(ruby_config.block_comment(), None);
2646
2647 buffer
2648 });
2649}
2650
2651#[gpui::test]
2652fn test_language_at_with_hidden_languages(cx: &mut App) {
2653 init_settings(cx, |_| {});
2654
2655 cx.new(|cx| {
2656 let text = r#"
2657 this is an *emphasized* word.
2658 "#
2659 .unindent();
2660
2661 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2662 language_registry.add(Arc::new(markdown_lang()));
2663 language_registry.add(Arc::new(markdown_inline_lang()));
2664
2665 let mut buffer = Buffer::local(text, cx);
2666 buffer.set_language_registry(language_registry.clone());
2667 buffer.set_language(
2668 language_registry
2669 .language_for_name("Markdown")
2670 .now_or_never()
2671 .unwrap()
2672 .ok(),
2673 cx,
2674 );
2675
2676 let snapshot = buffer.snapshot();
2677
2678 for point in [Point::new(0, 4), Point::new(0, 16)] {
2679 let config = snapshot.language_scope_at(point).unwrap();
2680 assert_eq!(config.language_name(), "Markdown".into());
2681
2682 let language = snapshot.language_at(point).unwrap();
2683 assert_eq!(language.name().as_ref(), "Markdown");
2684 }
2685
2686 buffer
2687 });
2688}
2689
2690#[gpui::test]
2691fn test_language_at_for_markdown_code_block(cx: &mut App) {
2692 init_settings(cx, |_| {});
2693
2694 cx.new(|cx| {
2695 let text = r#"
2696 ```rs
2697 let a = 2;
2698 // let b = 3;
2699 ```
2700 "#
2701 .unindent();
2702
2703 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2704 language_registry.add(Arc::new(markdown_lang()));
2705 language_registry.add(Arc::new(markdown_inline_lang()));
2706 language_registry.add(Arc::new(rust_lang()));
2707
2708 let mut buffer = Buffer::local(text, cx);
2709 buffer.set_language_registry(language_registry.clone());
2710 buffer.set_language(
2711 language_registry
2712 .language_for_name("Markdown")
2713 .now_or_never()
2714 .unwrap()
2715 .ok(),
2716 cx,
2717 );
2718
2719 let snapshot = buffer.snapshot();
2720
2721 // Test points in the code line
2722 for point in [Point::new(1, 4), Point::new(1, 6)] {
2723 let config = snapshot.language_scope_at(point).unwrap();
2724 assert_eq!(config.language_name(), "Rust".into());
2725
2726 let language = snapshot.language_at(point).unwrap();
2727 assert_eq!(language.name().as_ref(), "Rust");
2728 }
2729
2730 // Test points in the comment line to verify it's still detected as Rust
2731 for point in [Point::new(2, 4), Point::new(2, 6)] {
2732 let config = snapshot.language_scope_at(point).unwrap();
2733 assert_eq!(config.language_name(), "Rust".into());
2734
2735 let language = snapshot.language_at(point).unwrap();
2736 assert_eq!(language.name().as_ref(), "Rust");
2737 }
2738
2739 buffer
2740 });
2741}
2742
2743#[gpui::test]
2744fn test_serialization(cx: &mut gpui::App) {
2745 let mut now = Instant::now();
2746
2747 let buffer1 = cx.new(|cx| {
2748 let mut buffer = Buffer::local("abc", cx);
2749 buffer.edit([(3..3, "D")], None, cx);
2750
2751 now += Duration::from_secs(1);
2752 buffer.start_transaction_at(now);
2753 buffer.edit([(4..4, "E")], None, cx);
2754 buffer.end_transaction_at(now, cx);
2755 assert_eq!(buffer.text(), "abcDE");
2756
2757 buffer.undo(cx);
2758 assert_eq!(buffer.text(), "abcD");
2759
2760 buffer.edit([(4..4, "F")], None, cx);
2761 assert_eq!(buffer.text(), "abcDF");
2762 buffer
2763 });
2764 assert_eq!(buffer1.read(cx).text(), "abcDF");
2765
2766 let state = buffer1.read(cx).to_proto(cx);
2767 let ops = cx
2768 .background_executor()
2769 .block(buffer1.read(cx).serialize_ops(None, cx));
2770 let buffer2 = cx.new(|cx| {
2771 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2772 buffer.apply_ops(
2773 ops.into_iter()
2774 .map(|op| proto::deserialize_operation(op).unwrap()),
2775 cx,
2776 );
2777 buffer
2778 });
2779 assert_eq!(buffer2.read(cx).text(), "abcDF");
2780}
2781
2782#[gpui::test]
2783fn test_branch_and_merge(cx: &mut TestAppContext) {
2784 cx.update(|cx| init_settings(cx, |_| {}));
2785
2786 let base = cx.new(|cx| Buffer::local("one\ntwo\nthree\n", cx));
2787
2788 // Create a remote replica of the base buffer.
2789 let base_replica = cx.new(|cx| {
2790 Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
2791 });
2792 base.update(cx, |_buffer, cx| {
2793 cx.subscribe(&base_replica, |this, _, event, cx| {
2794 if let BufferEvent::Operation {
2795 operation,
2796 is_local: true,
2797 } = event
2798 {
2799 this.apply_ops([operation.clone()], cx);
2800 }
2801 })
2802 .detach();
2803 });
2804
2805 // Create a branch, which initially has the same state as the base buffer.
2806 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2807 branch.read_with(cx, |buffer, _| {
2808 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2809 });
2810
2811 // Edits to the branch are not applied to the base.
2812 branch.update(cx, |buffer, cx| {
2813 buffer.edit(
2814 [
2815 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
2816 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
2817 ],
2818 None,
2819 cx,
2820 )
2821 });
2822 branch.read_with(cx, |buffer, cx| {
2823 assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
2824 assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
2825 });
2826
2827 // Convert from branch buffer ranges to the corresponding ranges in the
2828 // base buffer.
2829 branch.read_with(cx, |buffer, cx| {
2830 assert_eq!(
2831 buffer.range_to_version(4..7, &base.read(cx).version()),
2832 4..4
2833 );
2834 assert_eq!(
2835 buffer.range_to_version(2..9, &base.read(cx).version()),
2836 2..5
2837 );
2838 });
2839
2840 // Edits to the base are applied to the branch.
2841 base.update(cx, |buffer, cx| {
2842 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
2843 });
2844 branch.read_with(cx, |buffer, cx| {
2845 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
2846 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
2847 });
2848
2849 // Edits to any replica of the base are applied to the branch.
2850 base_replica.update(cx, |buffer, cx| {
2851 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
2852 });
2853 branch.read_with(cx, |buffer, cx| {
2854 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
2855 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2856 });
2857
2858 // Merging the branch applies all of its changes to the base.
2859 branch.update(cx, |buffer, cx| {
2860 buffer.merge_into_base(Vec::new(), cx);
2861 });
2862
2863 branch.update(cx, |buffer, cx| {
2864 assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2865 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2866 });
2867}
2868
2869#[gpui::test]
2870fn test_merge_into_base(cx: &mut TestAppContext) {
2871 cx.update(|cx| init_settings(cx, |_| {}));
2872
2873 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2874 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2875
2876 // Make 3 edits, merge one into the base.
2877 branch.update(cx, |branch, cx| {
2878 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
2879 branch.merge_into_base(vec![5..8], cx);
2880 });
2881
2882 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
2883 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2884
2885 // Undo the one already-merged edit. Merge that into the base.
2886 branch.update(cx, |branch, cx| {
2887 branch.edit([(7..9, "hi")], None, cx);
2888 branch.merge_into_base(vec![5..8], cx);
2889 });
2890 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2891
2892 // Merge an insertion into the base.
2893 branch.update(cx, |branch, cx| {
2894 branch.merge_into_base(vec![11..11], cx);
2895 });
2896
2897 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
2898 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
2899
2900 // Deleted the inserted text and merge that into the base.
2901 branch.update(cx, |branch, cx| {
2902 branch.edit([(11..14, "")], None, cx);
2903 branch.merge_into_base(vec![10..11], cx);
2904 });
2905
2906 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2907}
2908
2909#[gpui::test]
2910fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
2911 cx.update(|cx| init_settings(cx, |_| {}));
2912
2913 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2914 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2915
2916 // Make 2 edits, merge one into the base.
2917 branch.update(cx, |branch, cx| {
2918 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
2919 branch.merge_into_base(vec![7..7], cx);
2920 });
2921 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2922 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2923
2924 // Undo the merge in the base buffer.
2925 base.update(cx, |base, cx| {
2926 base.undo(cx);
2927 });
2928 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2929 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2930
2931 // Merge that operation into the base again.
2932 branch.update(cx, |branch, cx| {
2933 branch.merge_into_base(vec![7..7], cx);
2934 });
2935 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2936 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2937}
2938
2939#[gpui::test]
2940async fn test_preview_edits(cx: &mut TestAppContext) {
2941 cx.update(|cx| {
2942 init_settings(cx, |_| {});
2943 theme::init(theme::LoadThemes::JustBase, cx);
2944 });
2945
2946 let insertion_style = HighlightStyle {
2947 background_color: Some(cx.read(|cx| cx.theme().status().created_background)),
2948 ..Default::default()
2949 };
2950 let deletion_style = HighlightStyle {
2951 background_color: Some(cx.read(|cx| cx.theme().status().deleted_background)),
2952 ..Default::default()
2953 };
2954
2955 // no edits
2956 assert_preview_edits(
2957 indoc! {"
2958 fn test_empty() -> bool {
2959 false
2960 }"
2961 },
2962 vec![],
2963 true,
2964 cx,
2965 |hl| {
2966 assert!(hl.text.is_empty());
2967 assert!(hl.highlights.is_empty());
2968 },
2969 )
2970 .await;
2971
2972 // only insertions
2973 assert_preview_edits(
2974 indoc! {"
2975 fn calculate_area(: f64) -> f64 {
2976 std::f64::consts::PI * .powi(2)
2977 }"
2978 },
2979 vec![
2980 (Point::new(0, 18)..Point::new(0, 18), "radius"),
2981 (Point::new(1, 27)..Point::new(1, 27), "radius"),
2982 ],
2983 true,
2984 cx,
2985 |hl| {
2986 assert_eq!(
2987 hl.text,
2988 indoc! {"
2989 fn calculate_area(radius: f64) -> f64 {
2990 std::f64::consts::PI * radius.powi(2)"
2991 }
2992 );
2993
2994 assert_eq!(hl.highlights.len(), 2);
2995 assert_eq!(hl.highlights[0], ((18..24), insertion_style));
2996 assert_eq!(hl.highlights[1], ((67..73), insertion_style));
2997 },
2998 )
2999 .await;
3000
3001 // insertions & deletions
3002 assert_preview_edits(
3003 indoc! {"
3004 struct Person {
3005 first_name: String,
3006 }
3007
3008 impl Person {
3009 fn first_name(&self) -> &String {
3010 &self.first_name
3011 }
3012 }"
3013 },
3014 vec![
3015 (Point::new(1, 4)..Point::new(1, 9), "last"),
3016 (Point::new(5, 7)..Point::new(5, 12), "last"),
3017 (Point::new(6, 14)..Point::new(6, 19), "last"),
3018 ],
3019 true,
3020 cx,
3021 |hl| {
3022 assert_eq!(
3023 hl.text,
3024 indoc! {"
3025 firstlast_name: String,
3026 }
3027
3028 impl Person {
3029 fn firstlast_name(&self) -> &String {
3030 &self.firstlast_name"
3031 }
3032 );
3033
3034 assert_eq!(hl.highlights.len(), 6);
3035 assert_eq!(hl.highlights[0], ((4..9), deletion_style));
3036 assert_eq!(hl.highlights[1], ((9..13), insertion_style));
3037 assert_eq!(hl.highlights[2], ((52..57), deletion_style));
3038 assert_eq!(hl.highlights[3], ((57..61), insertion_style));
3039 assert_eq!(hl.highlights[4], ((101..106), deletion_style));
3040 assert_eq!(hl.highlights[5], ((106..110), insertion_style));
3041 },
3042 )
3043 .await;
3044
3045 async fn assert_preview_edits(
3046 text: &str,
3047 edits: Vec<(Range<Point>, &str)>,
3048 include_deletions: bool,
3049 cx: &mut TestAppContext,
3050 assert_fn: impl Fn(HighlightedText),
3051 ) {
3052 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3053 let edits = buffer.read_with(cx, |buffer, _| {
3054 edits
3055 .into_iter()
3056 .map(|(range, text)| {
3057 (
3058 buffer.anchor_before(range.start)..buffer.anchor_after(range.end),
3059 text.to_string(),
3060 )
3061 })
3062 .collect::<Vec<_>>()
3063 });
3064 let edit_preview = buffer
3065 .read_with(cx, |buffer, cx| {
3066 buffer.preview_edits(edits.clone().into(), cx)
3067 })
3068 .await;
3069 let highlighted_edits = cx.read(|cx| {
3070 edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, include_deletions, cx)
3071 });
3072 assert_fn(highlighted_edits);
3073 }
3074}
3075
3076#[gpui::test(iterations = 100)]
3077fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
3078 let min_peers = env::var("MIN_PEERS")
3079 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
3080 .unwrap_or(1);
3081 let max_peers = env::var("MAX_PEERS")
3082 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
3083 .unwrap_or(5);
3084 let operations = env::var("OPERATIONS")
3085 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3086 .unwrap_or(10);
3087
3088 let base_text_len = rng.random_range(0..10);
3089 let base_text = RandomCharIter::new(&mut rng)
3090 .take(base_text_len)
3091 .collect::<String>();
3092 let mut replica_ids = Vec::new();
3093 let mut buffers = Vec::new();
3094 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
3095 let base_buffer = cx.new(|cx| Buffer::local(base_text.as_str(), cx));
3096
3097 for i in 0..rng.random_range(min_peers..=max_peers) {
3098 let buffer = cx.new(|cx| {
3099 let state = base_buffer.read(cx).to_proto(cx);
3100 let ops = cx
3101 .background_executor()
3102 .block(base_buffer.read(cx).serialize_ops(None, cx));
3103 let mut buffer =
3104 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
3105 buffer.apply_ops(
3106 ops.into_iter()
3107 .map(|op| proto::deserialize_operation(op).unwrap()),
3108 cx,
3109 );
3110 buffer.set_group_interval(Duration::from_millis(rng.random_range(0..=200)));
3111 let network = network.clone();
3112 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
3113 if let BufferEvent::Operation {
3114 operation,
3115 is_local: true,
3116 } = event
3117 {
3118 network.lock().broadcast(
3119 buffer.replica_id(),
3120 vec![proto::serialize_operation(operation)],
3121 );
3122 }
3123 })
3124 .detach();
3125 buffer
3126 });
3127
3128 buffers.push(buffer);
3129 replica_ids.push(i as ReplicaId);
3130 network.lock().add_peer(i as ReplicaId);
3131 log::info!("Adding initial peer with replica id {}", i);
3132 }
3133
3134 log::info!("initial text: {:?}", base_text);
3135
3136 let mut now = Instant::now();
3137 let mut mutation_count = operations;
3138 let mut next_diagnostic_id = 0;
3139 let mut active_selections = BTreeMap::default();
3140 loop {
3141 let replica_index = rng.random_range(0..replica_ids.len());
3142 let replica_id = replica_ids[replica_index];
3143 let buffer = &mut buffers[replica_index];
3144 let mut new_buffer = None;
3145 match rng.random_range(0..100) {
3146 0..=29 if mutation_count != 0 => {
3147 buffer.update(cx, |buffer, cx| {
3148 buffer.start_transaction_at(now);
3149 buffer.randomly_edit(&mut rng, 5, cx);
3150 buffer.end_transaction_at(now, cx);
3151 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
3152 });
3153 mutation_count -= 1;
3154 }
3155 30..=39 if mutation_count != 0 => {
3156 buffer.update(cx, |buffer, cx| {
3157 if rng.random_bool(0.2) {
3158 log::info!("peer {} clearing active selections", replica_id);
3159 active_selections.remove(&replica_id);
3160 buffer.remove_active_selections(cx);
3161 } else {
3162 let mut selections = Vec::new();
3163 for id in 0..rng.random_range(1..=5) {
3164 let range = buffer.random_byte_range(0, &mut rng);
3165 selections.push(Selection {
3166 id,
3167 start: buffer.anchor_before(range.start),
3168 end: buffer.anchor_before(range.end),
3169 reversed: false,
3170 goal: SelectionGoal::None,
3171 });
3172 }
3173 let selections: Arc<[Selection<Anchor>]> = selections.into();
3174 log::info!(
3175 "peer {} setting active selections: {:?}",
3176 replica_id,
3177 selections
3178 );
3179 active_selections.insert(replica_id, selections.clone());
3180 buffer.set_active_selections(selections, false, Default::default(), cx);
3181 }
3182 });
3183 mutation_count -= 1;
3184 }
3185 40..=49 if mutation_count != 0 && replica_id == 0 => {
3186 let entry_count = rng.random_range(1..=5);
3187 buffer.update(cx, |buffer, cx| {
3188 let diagnostics = DiagnosticSet::new(
3189 (0..entry_count).map(|_| {
3190 let range = buffer.random_byte_range(0, &mut rng);
3191 let range = range.to_point_utf16(buffer);
3192 let range = range.start..range.end;
3193 DiagnosticEntry {
3194 range,
3195 diagnostic: Diagnostic {
3196 message: post_inc(&mut next_diagnostic_id).to_string(),
3197 ..Default::default()
3198 },
3199 }
3200 }),
3201 buffer,
3202 );
3203 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
3204 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
3205 });
3206 mutation_count -= 1;
3207 }
3208 50..=59 if replica_ids.len() < max_peers => {
3209 let old_buffer_state = buffer.read(cx).to_proto(cx);
3210 let old_buffer_ops = cx
3211 .background_executor()
3212 .block(buffer.read(cx).serialize_ops(None, cx));
3213 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
3214 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
3215 .choose(&mut rng)
3216 .unwrap();
3217 log::info!(
3218 "Adding new replica {} (replicating from {})",
3219 new_replica_id,
3220 replica_id
3221 );
3222 new_buffer = Some(cx.new(|cx| {
3223 let mut new_buffer = Buffer::from_proto(
3224 new_replica_id,
3225 Capability::ReadWrite,
3226 old_buffer_state,
3227 None,
3228 )
3229 .unwrap();
3230 new_buffer.apply_ops(
3231 old_buffer_ops
3232 .into_iter()
3233 .map(|op| deserialize_operation(op).unwrap()),
3234 cx,
3235 );
3236 log::info!(
3237 "New replica {} text: {:?}",
3238 new_buffer.replica_id(),
3239 new_buffer.text()
3240 );
3241 new_buffer.set_group_interval(Duration::from_millis(rng.random_range(0..=200)));
3242 let network = network.clone();
3243 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
3244 if let BufferEvent::Operation {
3245 operation,
3246 is_local: true,
3247 } = event
3248 {
3249 network.lock().broadcast(
3250 buffer.replica_id(),
3251 vec![proto::serialize_operation(operation)],
3252 );
3253 }
3254 })
3255 .detach();
3256 new_buffer
3257 }));
3258 network.lock().replicate(replica_id, new_replica_id);
3259
3260 if new_replica_id as usize == replica_ids.len() {
3261 replica_ids.push(new_replica_id);
3262 } else {
3263 let new_buffer = new_buffer.take().unwrap();
3264 while network.lock().has_unreceived(new_replica_id) {
3265 let ops = network
3266 .lock()
3267 .receive(new_replica_id)
3268 .into_iter()
3269 .map(|op| proto::deserialize_operation(op).unwrap());
3270 if ops.len() > 0 {
3271 log::info!(
3272 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
3273 new_replica_id,
3274 buffer.read(cx).version(),
3275 ops.len(),
3276 ops
3277 );
3278 new_buffer.update(cx, |new_buffer, cx| {
3279 new_buffer.apply_ops(ops, cx);
3280 });
3281 }
3282 }
3283 buffers[new_replica_id as usize] = new_buffer;
3284 }
3285 }
3286 60..=69 if mutation_count != 0 => {
3287 buffer.update(cx, |buffer, cx| {
3288 buffer.randomly_undo_redo(&mut rng, cx);
3289 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
3290 });
3291 mutation_count -= 1;
3292 }
3293 _ if network.lock().has_unreceived(replica_id) => {
3294 let ops = network
3295 .lock()
3296 .receive(replica_id)
3297 .into_iter()
3298 .map(|op| proto::deserialize_operation(op).unwrap());
3299 if ops.len() > 0 {
3300 log::info!(
3301 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
3302 replica_id,
3303 buffer.read(cx).version(),
3304 ops.len(),
3305 ops
3306 );
3307 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
3308 }
3309 }
3310 _ => {}
3311 }
3312
3313 now += Duration::from_millis(rng.random_range(0..=200));
3314 buffers.extend(new_buffer);
3315
3316 for buffer in &buffers {
3317 buffer.read(cx).check_invariants();
3318 }
3319
3320 if mutation_count == 0 && network.lock().is_idle() {
3321 break;
3322 }
3323 }
3324
3325 let first_buffer = buffers[0].read(cx).snapshot();
3326 for buffer in &buffers[1..] {
3327 let buffer = buffer.read(cx).snapshot();
3328 assert_eq!(
3329 buffer.version(),
3330 first_buffer.version(),
3331 "Replica {} version != Replica 0 version",
3332 buffer.replica_id()
3333 );
3334 assert_eq!(
3335 buffer.text(),
3336 first_buffer.text(),
3337 "Replica {} text != Replica 0 text",
3338 buffer.replica_id()
3339 );
3340 assert_eq!(
3341 buffer
3342 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
3343 .collect::<Vec<_>>(),
3344 first_buffer
3345 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
3346 .collect::<Vec<_>>(),
3347 "Replica {} diagnostics != Replica 0 diagnostics",
3348 buffer.replica_id()
3349 );
3350 }
3351
3352 for buffer in &buffers {
3353 let buffer = buffer.read(cx).snapshot();
3354 let actual_remote_selections = buffer
3355 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
3356 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
3357 .collect::<Vec<_>>();
3358 let expected_remote_selections = active_selections
3359 .iter()
3360 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
3361 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
3362 .collect::<Vec<_>>();
3363 assert_eq!(
3364 actual_remote_selections,
3365 expected_remote_selections,
3366 "Replica {} remote selections != expected selections",
3367 buffer.replica_id()
3368 );
3369 }
3370}
3371
3372#[test]
3373fn test_contiguous_ranges() {
3374 assert_eq!(
3375 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
3376 &[1..4, 5..7, 9..13]
3377 );
3378
3379 // Respects the `max_len` parameter
3380 assert_eq!(
3381 contiguous_ranges(
3382 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
3383 3
3384 )
3385 .collect::<Vec<_>>(),
3386 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
3387 );
3388}
3389
3390#[gpui::test(iterations = 500)]
3391fn test_trailing_whitespace_ranges(mut rng: StdRng) {
3392 // Generate a random multi-line string containing
3393 // some lines with trailing whitespace.
3394 let mut text = String::new();
3395 for _ in 0..rng.random_range(0..16) {
3396 for _ in 0..rng.random_range(0..36) {
3397 text.push(match rng.random_range(0..10) {
3398 0..=1 => ' ',
3399 3 => '\t',
3400 _ => rng.random_range('a'..='z'),
3401 });
3402 }
3403 text.push('\n');
3404 }
3405
3406 match rng.random_range(0..10) {
3407 // sometimes remove the last newline
3408 0..=1 => drop(text.pop()), //
3409
3410 // sometimes add extra newlines
3411 2..=3 => text.push_str(&"\n".repeat(rng.random_range(1..5))),
3412 _ => {}
3413 }
3414
3415 let rope = Rope::from(text.as_str());
3416 let actual_ranges = trailing_whitespace_ranges(&rope);
3417 let expected_ranges = TRAILING_WHITESPACE_REGEX
3418 .find_iter(&text)
3419 .map(|m| m.range())
3420 .collect::<Vec<_>>();
3421 assert_eq!(
3422 actual_ranges,
3423 expected_ranges,
3424 "wrong ranges for text lines:\n{:?}",
3425 text.split('\n').collect::<Vec<_>>()
3426 );
3427}
3428
3429#[gpui::test]
3430fn test_words_in_range(cx: &mut gpui::App) {
3431 init_settings(cx, |_| {});
3432
3433 // The first line are words excluded from the results with heuristics, we do not expect them in the test assertions.
3434 let contents = r#"
34350_isize 123 3.4 4
3436let word=öäpple.bar你 Öäpple word2-öÄpPlE-Pizza-word ÖÄPPLE word
3437 "#;
3438
3439 let buffer = cx.new(|cx| {
3440 let buffer = Buffer::local(contents, cx).with_language(Arc::new(rust_lang()), cx);
3441 assert_eq!(buffer.text(), contents);
3442 buffer.check_invariants();
3443 buffer
3444 });
3445
3446 buffer.update(cx, |buffer, _| {
3447 let snapshot = buffer.snapshot();
3448 assert_eq!(
3449 BTreeSet::from_iter(["Pizza".to_string()]),
3450 snapshot
3451 .words_in_range(WordsQuery {
3452 fuzzy_contents: Some("piz"),
3453 skip_digits: true,
3454 range: 0..snapshot.len(),
3455 })
3456 .into_keys()
3457 .collect::<BTreeSet<_>>()
3458 );
3459 assert_eq!(
3460 BTreeSet::from_iter([
3461 "öäpple".to_string(),
3462 "Öäpple".to_string(),
3463 "öÄpPlE".to_string(),
3464 "ÖÄPPLE".to_string(),
3465 ]),
3466 snapshot
3467 .words_in_range(WordsQuery {
3468 fuzzy_contents: Some("öp"),
3469 skip_digits: true,
3470 range: 0..snapshot.len(),
3471 })
3472 .into_keys()
3473 .collect::<BTreeSet<_>>()
3474 );
3475 assert_eq!(
3476 BTreeSet::from_iter([
3477 "öÄpPlE".to_string(),
3478 "Öäpple".to_string(),
3479 "ÖÄPPLE".to_string(),
3480 "öäpple".to_string(),
3481 ]),
3482 snapshot
3483 .words_in_range(WordsQuery {
3484 fuzzy_contents: Some("öÄ"),
3485 skip_digits: true,
3486 range: 0..snapshot.len(),
3487 })
3488 .into_keys()
3489 .collect::<BTreeSet<_>>()
3490 );
3491 assert_eq!(
3492 BTreeSet::default(),
3493 snapshot
3494 .words_in_range(WordsQuery {
3495 fuzzy_contents: Some("öÄ好"),
3496 skip_digits: true,
3497 range: 0..snapshot.len(),
3498 })
3499 .into_keys()
3500 .collect::<BTreeSet<_>>()
3501 );
3502 assert_eq!(
3503 BTreeSet::from_iter(["bar你".to_string(),]),
3504 snapshot
3505 .words_in_range(WordsQuery {
3506 fuzzy_contents: Some("你"),
3507 skip_digits: true,
3508 range: 0..snapshot.len(),
3509 })
3510 .into_keys()
3511 .collect::<BTreeSet<_>>()
3512 );
3513 assert_eq!(
3514 BTreeSet::default(),
3515 snapshot
3516 .words_in_range(WordsQuery {
3517 fuzzy_contents: Some(""),
3518 skip_digits: true,
3519 range: 0..snapshot.len(),
3520 },)
3521 .into_keys()
3522 .collect::<BTreeSet<_>>()
3523 );
3524 assert_eq!(
3525 BTreeSet::from_iter([
3526 "bar你".to_string(),
3527 "öÄpPlE".to_string(),
3528 "Öäpple".to_string(),
3529 "ÖÄPPLE".to_string(),
3530 "öäpple".to_string(),
3531 "let".to_string(),
3532 "Pizza".to_string(),
3533 "word".to_string(),
3534 "word2".to_string(),
3535 ]),
3536 snapshot
3537 .words_in_range(WordsQuery {
3538 fuzzy_contents: None,
3539 skip_digits: true,
3540 range: 0..snapshot.len(),
3541 })
3542 .into_keys()
3543 .collect::<BTreeSet<_>>()
3544 );
3545 assert_eq!(
3546 BTreeSet::from_iter([
3547 "0_isize".to_string(),
3548 "123".to_string(),
3549 "3".to_string(),
3550 "4".to_string(),
3551 "bar你".to_string(),
3552 "öÄpPlE".to_string(),
3553 "Öäpple".to_string(),
3554 "ÖÄPPLE".to_string(),
3555 "öäpple".to_string(),
3556 "let".to_string(),
3557 "Pizza".to_string(),
3558 "word".to_string(),
3559 "word2".to_string(),
3560 ]),
3561 snapshot
3562 .words_in_range(WordsQuery {
3563 fuzzy_contents: None,
3564 skip_digits: false,
3565 range: 0..snapshot.len(),
3566 })
3567 .into_keys()
3568 .collect::<BTreeSet<_>>()
3569 );
3570 });
3571}
3572
3573fn ruby_lang() -> Language {
3574 Language::new(
3575 LanguageConfig {
3576 name: "Ruby".into(),
3577 matcher: LanguageMatcher {
3578 path_suffixes: vec!["rb".to_string()],
3579 ..Default::default()
3580 },
3581 line_comments: vec!["# ".into()],
3582 ..Default::default()
3583 },
3584 Some(tree_sitter_ruby::LANGUAGE.into()),
3585 )
3586 .with_indents_query(
3587 r#"
3588 (class "end" @end) @indent
3589 (method "end" @end) @indent
3590 (rescue) @outdent
3591 (then) @indent
3592 "#,
3593 )
3594 .unwrap()
3595}
3596
3597fn html_lang() -> Language {
3598 Language::new(
3599 LanguageConfig {
3600 name: LanguageName::new("HTML"),
3601 block_comment: Some(BlockCommentConfig {
3602 start: "<!--".into(),
3603 prefix: "".into(),
3604 end: "-->".into(),
3605 tab_size: 0,
3606 }),
3607 ..Default::default()
3608 },
3609 Some(tree_sitter_html::LANGUAGE.into()),
3610 )
3611 .with_indents_query(
3612 "
3613 (element
3614 (start_tag) @start
3615 (end_tag)? @end) @indent
3616 ",
3617 )
3618 .unwrap()
3619 .with_injection_query(
3620 r#"
3621 (script_element
3622 (raw_text) @injection.content
3623 (#set! injection.language "javascript"))
3624 "#,
3625 )
3626 .unwrap()
3627}
3628
3629fn erb_lang() -> Language {
3630 Language::new(
3631 LanguageConfig {
3632 name: "ERB".into(),
3633 matcher: LanguageMatcher {
3634 path_suffixes: vec!["erb".to_string()],
3635 ..Default::default()
3636 },
3637 block_comment: Some(BlockCommentConfig {
3638 start: "<%#".into(),
3639 prefix: "".into(),
3640 end: "%>".into(),
3641 tab_size: 0,
3642 }),
3643 ..Default::default()
3644 },
3645 Some(tree_sitter_embedded_template::LANGUAGE.into()),
3646 )
3647 .with_injection_query(
3648 r#"
3649 (
3650 (code) @injection.content
3651 (#set! injection.language "ruby")
3652 (#set! injection.combined)
3653 )
3654
3655 (
3656 (content) @injection.content
3657 (#set! injection.language "html")
3658 (#set! injection.combined)
3659 )
3660 "#,
3661 )
3662 .unwrap()
3663}
3664
3665fn rust_lang() -> Language {
3666 Language::new(
3667 LanguageConfig {
3668 name: "Rust".into(),
3669 matcher: LanguageMatcher {
3670 path_suffixes: vec!["rs".to_string()],
3671 ..Default::default()
3672 },
3673 ..Default::default()
3674 },
3675 Some(tree_sitter_rust::LANGUAGE.into()),
3676 )
3677 .with_indents_query(
3678 r#"
3679 (call_expression) @indent
3680 (field_expression) @indent
3681 (_ "(" ")" @end) @indent
3682 (_ "{" "}" @end) @indent
3683 "#,
3684 )
3685 .unwrap()
3686 .with_brackets_query(
3687 r#"
3688 ("{" @open "}" @close)
3689 "#,
3690 )
3691 .unwrap()
3692 .with_text_object_query(
3693 r#"
3694 (function_item
3695 body: (_
3696 "{"
3697 (_)* @function.inside
3698 "}" )) @function.around
3699
3700 (line_comment)+ @comment.around
3701
3702 (block_comment) @comment.around
3703 "#,
3704 )
3705 .unwrap()
3706 .with_outline_query(
3707 r#"
3708 (line_comment) @annotation
3709
3710 (struct_item
3711 "struct" @context
3712 name: (_) @name) @item
3713 (enum_item
3714 "enum" @context
3715 name: (_) @name) @item
3716 (enum_variant
3717 name: (_) @name) @item
3718 (field_declaration
3719 name: (_) @name) @item
3720 (impl_item
3721 "impl" @context
3722 trait: (_)? @name
3723 "for"? @context
3724 type: (_) @name
3725 body: (_ "{" (_)* "}")) @item
3726 (function_item
3727 "fn" @context
3728 name: (_) @name) @item
3729 (mod_item
3730 "mod" @context
3731 name: (_) @name) @item
3732 "#,
3733 )
3734 .unwrap()
3735}
3736
3737fn json_lang() -> Language {
3738 Language::new(
3739 LanguageConfig {
3740 name: "Json".into(),
3741 matcher: LanguageMatcher {
3742 path_suffixes: vec!["js".to_string()],
3743 ..Default::default()
3744 },
3745 ..Default::default()
3746 },
3747 Some(tree_sitter_json::LANGUAGE.into()),
3748 )
3749}
3750
3751fn javascript_lang() -> Language {
3752 Language::new(
3753 LanguageConfig {
3754 name: "JavaScript".into(),
3755 ..Default::default()
3756 },
3757 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
3758 )
3759 .with_brackets_query(
3760 r#"
3761 ("{" @open "}" @close)
3762 ("(" @open ")" @close)
3763 "#,
3764 )
3765 .unwrap()
3766 .with_indents_query(
3767 r#"
3768 (object "}" @end) @indent
3769 "#,
3770 )
3771 .unwrap()
3772}
3773
3774pub fn markdown_lang() -> Language {
3775 Language::new(
3776 LanguageConfig {
3777 name: "Markdown".into(),
3778 matcher: LanguageMatcher {
3779 path_suffixes: vec!["md".into()],
3780 ..Default::default()
3781 },
3782 ..Default::default()
3783 },
3784 Some(tree_sitter_md::LANGUAGE.into()),
3785 )
3786 .with_injection_query(
3787 r#"
3788 (fenced_code_block
3789 (info_string
3790 (language) @injection.language)
3791 (code_fence_content) @injection.content)
3792
3793 ((inline) @injection.content
3794 (#set! injection.language "markdown-inline"))
3795 "#,
3796 )
3797 .unwrap()
3798}
3799
3800pub fn markdown_inline_lang() -> Language {
3801 Language::new(
3802 LanguageConfig {
3803 name: "Markdown-Inline".into(),
3804 hidden: true,
3805 ..LanguageConfig::default()
3806 },
3807 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
3808 )
3809 .with_highlights_query("(emphasis) @emphasis")
3810 .unwrap()
3811}
3812
3813fn get_tree_sexp(buffer: &Entity<Buffer>, cx: &mut gpui::TestAppContext) -> String {
3814 buffer.update(cx, |buffer, _| {
3815 let snapshot = buffer.snapshot();
3816 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
3817 layers[0].node().to_sexp()
3818 })
3819}
3820
3821// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
3822#[track_caller]
3823fn assert_bracket_pairs(
3824 selection_text: &'static str,
3825 bracket_pair_texts: Vec<&'static str>,
3826 language: Language,
3827 cx: &mut App,
3828) {
3829 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
3830 let buffer =
3831 cx.new(|cx| Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx));
3832 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
3833
3834 let selection_range = selection_ranges[0].clone();
3835
3836 let bracket_pairs = bracket_pair_texts
3837 .into_iter()
3838 .map(|pair_text| {
3839 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
3840 assert_eq!(bracket_text, expected_text);
3841 (ranges[0].clone(), ranges[1].clone())
3842 })
3843 .collect::<Vec<_>>();
3844
3845 assert_set_eq!(
3846 buffer
3847 .bracket_ranges(selection_range)
3848 .map(|pair| (pair.open_range, pair.close_range))
3849 .collect::<Vec<_>>(),
3850 bracket_pairs
3851 );
3852}
3853
3854fn init_settings(cx: &mut App, f: fn(&mut AllLanguageSettingsContent)) {
3855 let settings_store = SettingsStore::test(cx);
3856 cx.set_global(settings_store);
3857 crate::init(cx);
3858 cx.update_global::<SettingsStore, _>(|settings, cx| {
3859 settings.update_user_settings::<AllLanguageSettings>(cx, f);
3860 });
3861}