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